Estatística para Ciência de Dados#
Programa#
Objetivos: Fornecer conhecimento em descrição e sumarização de dados, probabilidade, inferência estatística, inferência Bayesiana e modelos de regressão, necessários para o desenvolvimento de procedimentos em ciências de dados.
Ementa:
Descritiva: Medidas de posição, Medidas de dispersão, Agrupamento de dados, Apresentação tabular, Representação Gráfica
Probabilidade: Distribuições de probabilidade, esperança, variância e covariância, Resultados assintóticos e suas aplicações.
Elementos de inferência estatística: Funções de evidência e verossimilhança, Procedimentos de estimação pontual, Intervalos de confiança e testes de hipóteses, Inferência baseada em simulação.
Inferência Bayesiana: O paradigma Bayesiano, Os diferentes tipos de prioris, Distribuições conjugadas, Estimação Bayesiana, Densidade preditiva.
Modelagem de Regressão: Modelos lineares, Seleção de modelos, Regressão multivariada.
Referências:
Casella, G. and Berger, R. (2002). Statistical Inference. 2nd Edition, Duxbury Press, Florida.
Migon, H. S., Gamerman, D. and Louzada, F. (2014). Statistical Inference: An Integrated Approach, Second Edition, CRC Press.
Caffo, B. (2016). Statistical Inference for Data Science. Leanpub. Disponível em https://leanpub.com/LittleInferenceBook
Alguns vídeos complementares sugeridos:
Playlist disciplina SME0803 Visualização e Exploração de Dados (Prof. Cibele Russo) https://youtube.com/playlist?list=PLt7qVSwRVn5YEIvaMb02IJVKCpauWV-s9
Análise Exploratória de Dados: Correlação de Pearson e Spearman (Prof. Francisco Rodrigues) https://www.youtube.com/watch?v=qqRUsY2Fu0A
… e outras que serão citadas ao longo do curso.
Aula 1. Visualização de dados - Análise descritiva#
Programa#
a. Medidas de posição ou localização
b. Medidas de dispersão
c. Agrupamento de dados
d. Apresentação tabular
e. Representação Gráfica
Referências e motivação:#
Seaborn: statistical data visualization: https://seaborn.pydata.org/index.html.
COVID-19 Dashboard by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University: https://coronavirus.jhu.edu/map.html
Capa do The New York Times em 21/02/2021: https://pbs.twimg.com/media/EuwfGryXAAE6zhc?format=jpg&name=large
20+ Electoral Maps Visualizing 2020 U.S. Presidential Election Results — DataViz Weekly Special Edition: https://www.anychart.com/blog/2020/11/06/election-maps-us-vote-live-results/
Gráfico de linhas: Impacto da pandemia na educação https://twitter.com/gabrielbcor/status/1499122242369863691/photo/1
Art of Stat: https://artofstat.com/web-apps
Histograma humano: https://banderson02.files.wordpress.com/2014/05/f11-17_height_in_human__c.jpg
Análise exploratória de dados#
Análise descritiva ou análise exploratória de dados (AED) tem como objetivos básicos:
explorar os dados para descobrir ou identificar aspectos ou padrões de maior interesse,
representar os dados de forma a destacar ou chamar a atenção para aspectos ou padrões que podem ou não se confirmar inferencialmente.
Tukey (1977) chama a análise exploratória de dados de trabalho de detetive, que busca pistas e evidência, e a análise confirmatória de dados é um trabalho judicial ou quase-judicial, que analisa e avalia a força das provas e da evidência.
Tukey também diz que: “A análise exploratória de dados nunca conta a história toda, mas nada é tão perfeito para ser considerado a pedra fundamental, um primeiro passo para a análise de dados”.
É importante salientar que a AED é um trabalho inicial, a pedra fundamental, e os resultados devem ser analisados com uma análise confirmatória.
Tukey, John W. (1977) Exploratory data analysis. Editora Addison-Wesley.
A natureza dos dados#
Nesta aula, e quase sempre neste curso de Estatística para Ciência de Dados, trataremos de dados retangulares, que tem nas linhas as unidades amostrais (exemplos, samples) e nas colunas as variáveis (atributos, features).
Tipos de variáveis#
Qualitativas (não-numéricas)
Nominais: sexo, cor da pele, fumante/não-fumante, adimplente/inadimplente
Ordinais: escolaridade (em categorias), grau de satisfação, idade (em faixas)
Quantitativas (numéricas)
Discretas: número de defeitos em uma peça, número de produtos contratados
Contínuas: peso, idade, pressão sanguínea, valor contratado de um produto
Medidas-resumo#
Medidas de posição
Média: boas propriedades estatísticas
Mediana: medida resistente
Moda: valor mais frequente
Quantis: caracterização da distribuição dos dados
Medidas de dispersão
Desvio-padrão
Variância
Amplitude (range)
Coeficiente de variação: medida de dispersão relativa
Assimetria: Assimetria da distribuição dos dados
Curtose: Achatamento da distribuição
Medidas de associação: Covariância, Coeficiente de correlação de Pearson, Coeficiente de correlação de Spearman
Medidas de posição#
Daqui em diante, vamos estabelecer \(X_1,\ldots, X_n\) é uma amostra aleatória e \(x_1,\ldots, x_n\) os dados observados dessa amostra. As medidas aqui apresentadas são amostrais e são obtidas a partir de \(x_1,\ldots, x_n\).
A média (amostral observada) é definida como
\(\bar{x} = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n} x_i}{n}}\)
Considere agora os dados ordenados \(x_{(1)},\ldots, x_{(n)}\), isto é,
\(x_{(1)} = min(x_1,\ldots, x_n)\) e \(x_{(n)} = max(x_1,\ldots, x_n)\).
Se \(n\) é ímpar, a posição central é \(c = (n + 1) / 2\). Se \(n\) é par, as posições centrais são \(c = n / 2\) e \(c + 1 = n / 2 + 1\).
A mediana é definida como
\(Md = \bigg \{\begin{array}{l}x_{(c)}, \mbox{se n é ímpar}\\ \displaystyle\frac{x_{(c)}+x_{(c+1)}}{2}, \mbox{se n é par}\end{array}\)
A moda é o valor mais frequente da amostra. Não necessariamente existe.
Um quantil é o valor que provoca uma divisão conveniente nos valores ordenados. O quantil de 10% divide os dados de tal forma que 10% dos menores valores fiquem “à sua esquerda”. O quantil de 50% é a mediana.
Os quartis dividem os dados em porções de 25%.
Os decis dividem os dados em porções de 10%.
Os percentis dividem os dados em porções de 1%.
Medidas de dispersão#
A variância amostral é dada por
\(s^2 = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n}}.\)
O desvio padrão é dado por
\(s = \displaystyle\sqrt{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n}}.\)
É comum, entretanto, utilizar as medidas corrigidas:
Variância amostral corrigida:
\(s^2 = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n-1}}\)
Desvio padrão corrigido:
\(s = \displaystyle\sqrt{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n-1}}\)
A amplitude é dada por
\(A = x_{(n)} -x_{(1)}.\)
O coeficiente de variação (amostral) é dado pela razão entre o desvio-padrão e a média
\(CV = \displaystyle{\frac{s}{\bar{x}}}\)
Assimetria#
Distribuição simétrica: média = mediana = moda
Distribuição assimétrica à direita: moda < mediana < média
Distribuição assimétrica à esquerda: média < mediana < moda
Curtose#
Distribuições mesocúrticas: achatamento da distribuição normal
Distribuições leptocúrticas: distribuição mais concentrada
Distribuições platicúrticas: distribuição mais achatada
# Ilustração das medidas média, moda, mediana para dados simétricos
# Adaptado de https://stackoverflow.com/questions/51417483/mean-median-mode-lines-showing-only-in-last-graph-in-seaborn/51417635
from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns
df = pd.DataFrame({"rating": [5, 6, 6, 7, 7, 7, 7, 8, 8, 9]})
f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw= {"height_ratios": (0.2, 1)})
mean=df['rating'].mean()
median=df['rating'].median()
mode=df['rating'].mode().values[0]
sns.boxplot(data=df, x="rating", ax=ax_box)
ax_box.axvline(mean, color='r', linestyle='--')
ax_box.axvline(median, color='g', linestyle='-')
ax_box.axvline(mode, color='b', linestyle='-')
sns.histplot(data=df, x="rating", ax=ax_hist, kde=True)
ax_hist.axvline(mean, color='r', linestyle='--', label="Mean")
ax_hist.axvline(median, color='g', linestyle='-', label="Median")
ax_hist.axvline(mode, color='b', linestyle='-', label="Mode")
plt.legend()
ax_box.set(xlabel='')
plt.show()
# Ilustração das medidas média, moda, mediana para dados assimétricos à direita ou assimetria positiva
# Adaptado de https://stackoverflow.com/questions/51417483/mean-median-mode-lines-showing-only-in-last-graph-in-seaborn/51417635
from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns
import statistics
df = pd.DataFrame({"rating": [1,1,1,2,2,3,4,5,5,10]})
f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw= {"height_ratios": (0.2, 1)})
mean=df['rating'].mean()
median=df['rating'].median()
mode=df['rating'].mode().values[0]
sns.boxplot(data=df, x="rating", ax=ax_box)
ax_box.axvline(mean, color='r', linestyle='--')
ax_box.axvline(median, color='g', linestyle='-')
ax_box.axvline(mode, color='b', linestyle='-')
sns.histplot(data=df, x="rating", ax=ax_hist, kde=True)
ax_hist.axvline(mean, color='r', linestyle='--', label="Mean")
ax_hist.axvline(median, color='g', linestyle='-', label="Median")
ax_hist.axvline(mode, color='b', linestyle='-', label="Mode")
plt.legend()
ax_box.set(xlabel='')
plt.show()
# Ilustração das medidas média, moda, mediana para dados assimétricos à esquerda ou com assimetria negativa
# Adaptado de: https://stackoverflow.com/questions/51417483/mean-median-mode-lines-showing-only-in-last-graph-in-seaborn/51417635
from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns
import statistics
df = pd.DataFrame({"rating": [1, 4, 6, 8, 8, 9, 10, 10, 10, 10]})
f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw= {"height_ratios": (0.2, 1)})
mean=df['rating'].mean()
median=df['rating'].median()
mode = statistics.mode(df['rating'])
sns.boxplot(data=df, x="rating", ax=ax_box)
ax_box.axvline(mean, color='b', linestyle='--')
ax_box.axvline(median, color='r', linestyle='-')
ax_box.axvline(mode, color='g', linestyle='-')
sns.histplot(data=df, x="rating", ax=ax_hist, kde=True)
ax_hist.axvline(mean, color='b', linestyle='--', label="Mean")
ax_hist.axvline(median, color='r', linestyle='-', label="Median")
ax_hist.axvline(mode, color='g', linestyle='-', label="Mode")
plt.legend()
ax_box.set(xlabel='')
plt.show()
Curtose#
Referências:
Medida que caracteriza o achatamento da curva.
Curtose \(\approx 0\): achatamento da curva normal
Curtose \(>0\): leptocúrtica, distribuição mais afunilada
Curtose \(<0\): platicúrtica, distribuição mais achatada
Obs: Distribuição normal https://www.spss-tutorials.com/normal-distribution/
from scipy.stats import norm, kurtosis
data = norm.rvs(size=100000)
kurtosis(data)
0.009731018571984329
# Fonte: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.kurtosis.html
import matplotlib.pyplot as plt
import scipy.stats as stats
from scipy.stats import kurtosis
import numpy as np
x = np.linspace(-5, 5, 100)
ax = plt.subplot()
distnames = ['laplace', 'norm', 'uniform']
for distname in distnames:
if distname == 'uniform':
dist = getattr(stats, distname)(loc=-2, scale=4)
else:
dist = getattr(stats, distname)
data = dist.rvs(size=1000)
kur = kurtosis(data, fisher=True)
y = dist.pdf(x)
ax.plot(x, y, label="{}, {}".format(distname, round(kur, 3)))
ax.legend()
# Normal: mesocúrtica
# Laplace: leptocúrtica
# Uniforme: platicúrtica
Medidas de associação entre variáveis quantitativas#
Sejam \(X\) e \(Y\) variáveis quantitativas de interesse e as amostras aleatórias observadas \(x_1,\ldots,x_n\) e \(y_1,\ldots,y_n\), respectivamente. As medidas de associação mais utilizadas são:
Covariância (amostral)#
\(s_{XY} = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})(y_i-\bar{y})}{n-1}}\)
Coeficiente de correlação linear (amostral) de Pearson#
Referência: https://pt.wikipedia.org/wiki/Coeficiente_de_correlação_de_Pearson
\(r = \displaystyle{\frac{s_{XY}}{\sqrt{s^2_X s^2_Y }}}\)
Propriedade:
\(-1 \leq r \leq 1\)
É comum usar as seguintes classificações:
\(r=1\) indica uma correlação perfeita e positiva
\(r=-1\) indica uma correlação perfeita e negativa
\(0.7 \leq |r| \leq 1\) indica uma correlação forte
\(0.5 \leq |r| \leq 0.69\) indica uma correlação moderada
\(0 \leq |r| \leq 0.49\) indica uma correlação fraca
Coeficiente de correlação de Spearman#
Avalia relações monótonas entre duas variáveis
Referência: https://pt.wikipedia.org/wiki/Coeficiente_de_correlação_de_postos_de_Spearman
Associação entre variáveis qualitativas e quantitativas#
Alguns casos que veremos mais adiante:
Associação entre variáveis quantitativas e qualitativas: Testes para comparação de médias em duas populações.
Associação entre variáveis qualitativas: Teste qui-quadrado, teste exato de Fisher, entre outros.
Representação gráfica e tabular de dados#
Variáveis qualitativas:
Tabela de frequência: Resume a informação dos dados de forma a possibilitar a observação de frequências absolutas ou relativas de cada categoria das variáveis qualitativas (ou valores assumidos pelas variáveis quantitativas discretas).
Gráfico de barras Representação gráfica das frequências de cada categoria das variáveis qualitativas (ou valores assumidos pelas variáveis quantitativas discretas). As barras são separadas.
Gráfico de Pareto: Gráfico de barras + frequências acumuladas das categorias.
Gráfico de setores (pizza): Representação gráfica das proporções das categorias das variáveis quantitativas discretas.
Variáveis quantitativas discretas:
Tabelas de frequências
Gráficos de barras
Gráficos de pontos
Variáveis quantitativas contínuas:
Histogramas: Representação gráfica para uma aproximação da distribuição de uma variável quantitativa contínua, discretizada em classes de tamanhos convenientes. As barras são adjacentes. Permitem observar a localização, dispersão, assimetria, número de picos, curtose dos dados.
Gráficos de linhas (dados coletados ao longo do tempo)
Boxplots (gráficos de caixas): Representação gráfica inteligente que permite a observação da localização, dispersão, assimetria, pontos discrepantes (outliers). Além disso, permite comparar visualemente a distribuição de dados em dois grupos. Pode indicar evidências sobre a igualdade das médias entre os dados de dois grupos, pendente de análise confirmatória inferencial.

Representação tabular#
Tabelas que resumem a informação da base completa de dados.
Tabelas de frequências: Resumo dos dados originais considerando as frequências observadas na amostra, de variáveis qualitativas ou variáveis que foram categorizadas
Tabelas de dupla entrada: Avaliação da associação entre variáveis qualitativas ou que foram categorizadas.
Aplicação com visualização e exploração de dados#
Considere uma amostra de 10 mil clientes de um banco no arquivo dados_banco.csv. Estão disponíveis as variáveis:
Cliente: Identificador do cliente.
Sexo: Feminino (F) ou Masculino (M)
Idade: Idade do cliente, em anos completos.
Empresa: Tipo da empresa em que trabalha: Pública, Privada ou Autônomo
Salário: Salário declarado pelo cliente na abertura da conta, em reais.
Saldo_cc: Saldo em conta corrente, em reais.
Saldo_poupança: Saldo em poupança, em reais.
Saldo_investimento: Saldo em investimentos, em reais.
Devedor_cartao: Valor em atraso no cartão de crédito, em reais.
Inadimplente: Se o cliente é considerado inadimplente atualmente (1) ou não (0), de acordo com critérios preestabelecidos.
Desenvolva a exploração e visualização dos dados. Verifique possíveis associações entre variáveis.
import os.path
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats
%matplotlib inline
# Modifique o diretório para fazer a leitura dos dados em dados_banco.csv
# Dados banco - Leitura dos dados
# Caso necessário, leia a partir de um diretório da sua máquina
# pkgdir = '/hdd/MBA/ECD/Data'
# dados = pd.read_csv(f'{pkgdir}/dados_banco.csv', index_col=0)
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
dados
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|---|---|
| Cliente | |||||||||
| 75928 | M | 32 | Privada | 5719.00 | 933.79 | 0.0 | 0.0 | 6023.68 | 0 |
| 52921 | F | 28 | Privada | 5064.00 | 628.37 | 0.0 | 0.0 | 1578.24 | 0 |
| 8387 | F | 24 | Autônomo | 4739.00 | 889.18 | 0.0 | 0.0 | 2578.70 | 0 |
| 54522 | M | 30 | Pública | 5215.00 | 1141.47 | 0.0 | 0.0 | 4348.96 | 0 |
| 45397 | M | 30 | Autônomo | 5215.56 | 520.70 | 0.0 | 0.0 | 1516.78 | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 33487 | F | 31 | Pública | 5016.00 | 498.96 | 0.0 | 0.0 | 1263.34 | 0 |
| 71360 | M | 29 | Pública | 5329.00 | 1142.82 | 0.0 | 0.0 | 5613.71 | 0 |
| 92455 | M | 34 | Privada | 5581.00 | 885.34 | 0.0 | 0.0 | 1199.22 | 0 |
| 61296 | F | 28 | Privada | 5061.00 | 660.74 | 0.0 | 0.0 | 1152.97 | 0 |
| 52862 | M | 33 | Autônomo | 5519.00 | 1147.71 | 0.0 | 0.0 | 4684.66 | 0 |
10000 rows × 9 columns
dados.head()
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|---|---|
| Cliente | |||||||||
| 75928 | M | 32 | Privada | 5719.00 | 933.79 | 0.0 | 0.0 | 6023.68 | 0 |
| 52921 | F | 28 | Privada | 5064.00 | 628.37 | 0.0 | 0.0 | 1578.24 | 0 |
| 8387 | F | 24 | Autônomo | 4739.00 | 889.18 | 0.0 | 0.0 | 2578.70 | 0 |
| 54522 | M | 30 | Pública | 5215.00 | 1141.47 | 0.0 | 0.0 | 4348.96 | 0 |
| 45397 | M | 30 | Autônomo | 5215.56 | 520.70 | 0.0 | 0.0 | 1516.78 | 1 |
Classificação das variáveis por tipo#
Sexo: qualitativa nominal
Idade: quantitativa contínua
Empresa: qualitativa nominal
Salário: quantitativa contínua
Saldo_cc: quantitativa contínua
Saldo_poupança: quantitativa contínua
Saldo_investimento: quantitativa contínua
Devedor_cartão: quantitativa contínua
Inadimplente: qualitativa nominal (embora numérica)
Tabela de frequências (absolutas e relativas)#
(para a Empresa, repetir para outras variáveis qualitativas)
# Tabela de frequências absolutas
tab = pd.crosstab(index=dados['Empresa'], columns='count')
tab
| col_0 | count |
|---|---|
| Empresa | |
| Autônomo | 1447 |
| Privada | 6103 |
| Pública | 2450 |
tab = pd.crosstab(index=dados['Empresa'], columns='count')
# Tabela de frequências relativas
tab/tab.sum()
| col_0 | count |
|---|---|
| Empresa | |
| Autônomo | 0.1447 |
| Privada | 0.6103 |
| Pública | 0.2450 |
Análise: Na base de dados, cerca de 61% dos clientes trabalham em empresas privadas, 24% em empresas públicas e 15% são autônomos.
Medidas resumo#
(para a idade, poderia repetir para as outras variáveis quantitativas)
# Média
dados['Idade'].mean()
31.8019
# Mediana
dados['Idade'].median()
32.0
# Desvio-padrão
round(dados['Idade'].std(),2)
2.93
# Média de idade por grupos
dados.groupby('Sexo')['Idade'].mean()
Sexo
F 30.130466
M 33.027734
Name: Idade, dtype: float64
Análise: A média de idade nos dados é 31.8 anos, a mediana é 32 anos. O desvio-padrão da idade na base de dados geral é 2.93 anos. Entre mulheres, a média de idade é 30.1 anos e entre homens, 33 anos.
# Média de idade por grupos
dados.groupby('Empresa')['Idade'].mean()
Empresa
Autônomo 29.163787
Privada 32.867115
Pública 30.706531
Name: Idade, dtype: float64
Análise: A média de idade entre os clientes autônomos é de 29.1 anos, entre clientes que trabalham em empresas privadas é 32.9 anos e para clientes que trabalham em empresas públicas é 30.7 anos.
# Moda - para a Empresa
import statistics
statistics.mode(dados['Empresa'])
'Privada'
Análise: Na base de dados, o tipo de empresa mais comum é a empresa privada.
# Ordenação dos dados
np.sort(dados['Idade'])
array([21, 22, 22, ..., 49, 50, 50], dtype=int64)
# Quantis de 95% e 25%
np.percentile(dados['Idade'],95)
36.0
np.percentile(dados['Idade'],25)
30.0
sns.displot(x=dados['Idade'], height=4, kind='kde');
tab1 = pd.crosstab(index=dados['Sexo'], columns='count')
tab1/tab1.sum()
| col_0 | count |
|---|---|
| Sexo | |
| F | 0.4231 |
| M | 0.5769 |
Estatísticas descritivas dos dados com describe()#
dados.describe()
| Idade | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|
| count | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 |
| mean | 31.801900 | 5482.880238 | 773.441611 | 2224.517679 | 1476.939508 | 2737.210731 | 0.246100 |
| std | 2.931913 | 393.779438 | 246.932963 | 5668.740769 | 3920.049185 | 1994.877093 | 0.430759 |
| min | 21.000000 | 4325.720000 | -280.670000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 30.000000 | 5207.540000 | 599.425000 | 0.000000 | 0.000000 | 1186.807500 | 0.000000 |
| 50% | 32.000000 | 5498.780000 | 766.000000 | 0.000000 | 0.000000 | 2692.935000 | 0.000000 |
| 75% | 34.000000 | 5738.220000 | 941.470000 | 0.000000 | 0.000000 | 4058.565000 | 0.000000 |
| max | 50.000000 | 8582.000000 | 2007.260000 | 23336.420000 | 21810.520000 | 12312.220000 | 1.000000 |
dados.loc[:,dados.columns != 'Cliente'].describe()
| Idade | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|
| count | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 |
| mean | 31.801900 | 5482.880238 | 773.441611 | 2224.517679 | 1476.939508 | 2737.210731 | 0.246100 |
| std | 2.931913 | 393.779438 | 246.932963 | 5668.740769 | 3920.049185 | 1994.877093 | 0.430759 |
| min | 21.000000 | 4325.720000 | -280.670000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 30.000000 | 5207.540000 | 599.425000 | 0.000000 | 0.000000 | 1186.807500 | 0.000000 |
| 50% | 32.000000 | 5498.780000 | 766.000000 | 0.000000 | 0.000000 | 2692.935000 | 0.000000 |
| 75% | 34.000000 | 5738.220000 | 941.470000 | 0.000000 | 0.000000 | 4058.565000 | 0.000000 |
| max | 50.000000 | 8582.000000 | 2007.260000 | 23336.420000 | 21810.520000 | 12312.220000 | 1.000000 |
Gráfico de setores (pizza)#
tab
| col_0 | count |
|---|---|
| Empresa | |
| Autônomo | 1447 |
| Privada | 6103 |
| Pública | 2450 |
plot = tab.plot.pie(y='count')
#define Seaborn color palette to use
colors = sns.color_palette('pastel')[0:5]
plot = tab.plot.pie(y='count', colors=colors)
# Tabela de frequências absolutas
tab = pd.crosstab(index=dados['Sexo'], columns='count')
tab
| col_0 | count |
|---|---|
| Sexo | |
| F | 4231 |
| M | 5769 |
plot = tab.plot.pie(y='count', colors=colors)
Gráfico de barras#
tab.plot.bar()
plt.legend(title='Sexo')
plt.show()
Boxplot#
Posição
Dispersão
Outliers
Assimetria
https://seaborn.pydata.org/generated/seaborn.boxplot.html
dados['Salario']
Cliente
75928 5719.00
52921 5064.00
8387 4739.00
54522 5215.00
45397 5215.56
...
33487 5016.00
71360 5329.00
92455 5581.00
61296 5061.00
52862 5519.00
Name: Salario, Length: 10000, dtype: float64
sns.boxplot(y=dados['Salario'], palette='pastel')
<AxesSubplot:ylabel='Salario'>
Histograma
sns.displot(dados['Salario'],kde=False, bins=10, height=5, aspect=2);
sns.displot(dados['Salario']);
sns.displot(dados['Salario'], bins=10, height=5, aspect=2)
<seaborn.axisgrid.FacetGrid at 0x17c18c8c1f0>
Densidade alisada
sns.displot(dados['Salario'], kind='kde', height=5, aspect=2)
<seaborn.axisgrid.FacetGrid at 0x17c188bd4f0>
Associação entre duas variáveis qualitativas#
# Tabela de dupla entrada
tabela_dupla = pd.crosstab(index=dados['Empresa'], columns=dados['Sexo'])
tabela_dupla
| Sexo | F | M |
|---|---|---|
| Empresa | ||
| Autônomo | 875 | 572 |
| Privada | 2047 | 4056 |
| Pública | 1309 | 1141 |
tabela_dupla.plot.bar()
plt.legend(title='Sexo')
plt.show()
tabela_dupla.plot.bar(stacked=True)
plt.legend(title='Sexo')
plt.show()
Gráfico de mosaico#
from statsmodels.graphics.mosaicplot import mosaic
plt.rcParams["figure.figsize"] = [10, 5]
mosaic(dados,['Sexo', 'Empresa'] );
Associação entre variáveis quantitativas e qualitativas#
ax = sns.boxplot(x='Sexo', y='Salario', data=dados, palette='Set2')
plt.figure(figsize=(12,6))
ax = sns.boxplot(x='Sexo', y='Salario', hue='Empresa', data=dados, palette='colorblind')
ax = sns.boxplot(x='Empresa', y='Salario', hue='Sexo', data=dados, palette='Set2')
ax = sns.catplot(x='Sexo', y='Salario', kind='violin', data=dados, palette='Set2')
ax = sns.catplot(x='Sexo', y='Salario', hue='Empresa', kind='violin', data=dados, palette='Set2')
sns.catplot(x='Empresa', y='Salario', hue='Sexo', kind='violin', split=True, data=dados, palette='Set2')
<seaborn.axisgrid.FacetGrid at 0x17c1ac1ffd0>
# Salário médio por tipo de empresa
sns.set_theme(style="whitegrid")
# Estabelecendo o tamanho do gráfico
plt.figure(figsize=(8,4))
# Título
plt.title("Salário médio por tipo de empresa")
# Gráfico de barras com salário médio por tipo de empresa
sns.barplot(x='Empresa', y='Salario', data=dados, palette='Set2')
#sns.barplot(x='Empresa', y='Salario', hue='Sexo', data=dados, palette='Set2')
# Label para eixo vertical
plt.ylabel("Salário");
Associação entre variáveis quantitativas#
Gráfico de dispersão
sns.set_palette('colorblind')
sns.relplot(x='Idade', y='Salario', data=dados)
<seaborn.axisgrid.FacetGrid at 0x17c167a2220>
sns.set_palette('colorblind')
sns.relplot(x='Idade', y='Salario', hue='Sexo', col='Empresa', data=dados);
Gráficos com Regressão
sns.lmplot(x='Idade', y='Salario', hue='Sexo', col='Empresa', data=dados, aspect=1, ci=90);
sns.lmplot(x='Idade', y='Salario', hue='Empresa', col='Sexo', data=dados, aspect=1, ci=90);
Joint plot
sns.jointplot(x='Idade', y='Salario', data=dados);
sns.jointplot(x='Idade', y='Salario', kind='reg', data=dados);
sns.jointplot(x='Idade', y='Salario', kind='kde', data=dados);
Coeficiente de correlação de Pearson
from scipy.stats import pearsonr
pearsonr(dados['Idade'], dados['Salario'])[0]
0.8506660825874652
Heatmap (mapa de calor)#
dados_heatmap = dados.loc[:,dados.columns != 'Cliente'].groupby(['Idade']).mean()
dados_heatmap.head()
# Estabelecendo o tamanho do gráfico
plt.figure(figsize=(10,5))
ax = sns.heatmap(dados_heatmap)
# ax = sns.heatmap(dados_heatmap, cmap="BuPu")
Gráficos multivariados#
sns.pairplot(dados[['Salario','Saldo_cc', 'Saldo_poupança', 'Saldo_investimento', 'Devedor_cartao']])
<seaborn.axisgrid.PairGrid at 0x17c19ece670>
sns.pairplot(dados[['Salario','Saldo_cc', 'Saldo_poupança', 'Saldo_investimento', 'Devedor_cartao']], kind='reg')
<seaborn.axisgrid.PairGrid at 0x17c1ac9f070>
dados_nozeros = dados[dados['Saldo_investimento']*dados['Saldo_poupança']!=0]
sns.pairplot(dados_nozeros[['Salario','Saldo_cc', 'Saldo_poupança', 'Saldo_investimento', 'Devedor_cartao']], kind='reg')
<seaborn.axisgrid.PairGrid at 0x17c1e9d2220>
Agrupamento de dados#
Agrupamento hierárquico (dendrograma)
Agrupamento não-hierárquico (k-médias)
Referências:
Aulas no contexto de Análise Multivariada e Aprendizado Não-supervisionado (Profa. Cibele Russo):
Análise de Agrupamentos: https://youtu.be/zyLDAnQMnbo
Análise de Agrupamentos - Um exemplo passo a passo: https://youtu.be/Re97VX6ZhPA
Análise de Agrupamentos - Aplicação em Python: https://youtu.be/d_CJGaAbC7o
Exercício
Analise as possíveis associações entre o sexo, idade, empresa, salário, saldo em conta corrente, saldo em conta poupança, saldo em investimento e devedor no cartão com a variável Inadimplente.
ax = sns.boxplot(x='Sexo', y='Salario', hue='Inadimplente', data=dados, palette='muted')
ax = sns.boxplot(x='Sexo', y='Saldo_cc', hue='Inadimplente', data=dados, palette='muted')
ax = sns.boxplot(x='Sexo', y='Devedor_cartao', hue='Inadimplente', data=dados, palette='muted')
dados.loc[dados['Inadimplente']==0, 'Inadimplente']= 'Não'
dados.loc[dados['Inadimplente']==1, 'Inadimplente']= 'Sim'
sns.displot(dados, x='Devedor_cartao', col='Sexo', hue='Inadimplente', bins=30);
Pacote plotly#
# Instale se necessário
# AVISO: Os códigos abaixo podem não funcionar de imediato no google colab (somente no Jupyter), necessitando de adaptações.
#!pip install plotly==5.3.1
import plotly
import plotly.express as px
#fig = px.histogram(dados, x='Salario')
#fig.show()
#dados['Salario'].iplot(kind='hist')
fig = px.histogram(dados, x='Salario', marginal='box')
fig.show()
fig = px.box(dados, y='Salario', x='Sexo', color='Empresa')
fig.show()
fig = px.scatter(dados, x='Idade', y='Salario', color='Empresa')
fig.show()
fig = px.scatter(dados, x='Saldo_cc', y='Salario', color='Empresa')
fig.show()
AED de forma automatizada#
Usar somente se estritamente necessário ou como uma análise inicial!!
Referência: https://medium.com/geekculture/10-automated-eda-libraries-at-one-place-ea5d4c162bbb
# ATENÇÃO: Para executar o notebook no colab, no menu Runtime, selecione Restart runtime e execute normalmente os comandos abaixo
import pandas as pd
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
# Instale se necessário ou se for executar no Google colab
!pip install pandas_profiling
Collecting pandas_profiling
Using cached pandas_profiling-3.6.6-py2.py3-none-any.whl (324 kB)
Collecting ydata-profiling
Using cached ydata_profiling-4.6.4-py2.py3-none-any.whl (357 kB)
Collecting pydantic>=2
Using cached pydantic-2.5.3-py3-none-any.whl (381 kB)
Requirement already satisfied: numpy<1.26,>=1.16.0 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (1.24.4)
Requirement already satisfied: requests<3,>=2.24.0 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (2.28.1)
Collecting numba<0.59.0,>=0.56.0
Using cached numba-0.58.1-cp39-cp39-win_amd64.whl (2.6 MB)
Collecting typeguard<5,>=4.1.2
Using cached typeguard-4.1.5-py3-none-any.whl (34 kB)
Requirement already satisfied: seaborn<0.13,>=0.10.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (0.11.2)
Requirement already satisfied: tqdm<5,>=4.48.2 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (4.64.1)
Requirement already satisfied: jinja2<3.2,>=2.11.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (2.11.3)
Requirement already satisfied: htmlmin==0.1.12 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (0.1.12)
Requirement already satisfied: pandas!=1.4.0,<3,>1.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (1.4.4)
Collecting wordcloud>=1.9.1
Using cached wordcloud-1.9.3-cp39-cp39-win_amd64.whl (300 kB)
Requirement already satisfied: statsmodels<1,>=0.13.2 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (0.13.5)
Collecting imagehash==4.3.1
Using cached ImageHash-4.3.1-py2.py3-none-any.whl (296 kB)
Collecting phik<0.13,>=0.11.1
Using cached phik-0.12.4-cp39-cp39-win_amd64.whl (666 kB)
Collecting multimethod<2,>=1.4
Using cached multimethod-1.10-py3-none-any.whl (9.9 kB)
Requirement already satisfied: scipy<1.12,>=1.4.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (1.9.1)
Requirement already satisfied: PyYAML<6.1,>=5.0.0 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (6.0)
Requirement already satisfied: matplotlib<3.9,>=3.2 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (3.5.2)
Collecting visions[type_image_path]==0.7.5
Using cached visions-0.7.5-py3-none-any.whl (102 kB)
Collecting dacite>=1.8
Using cached dacite-1.8.1-py3-none-any.whl (14 kB)
Requirement already satisfied: PyWavelets in c:\users\imikemori\anaconda3\lib\site-packages (from imagehash==4.3.1->ydata-profiling->pandas_profiling) (1.3.0)
Requirement already satisfied: pillow in c:\users\imikemori\anaconda3\lib\site-packages (from imagehash==4.3.1->ydata-profiling->pandas_profiling) (9.2.0)
Requirement already satisfied: tangled-up-in-unicode>=0.0.4 in c:\users\imikemori\anaconda3\lib\site-packages (from visions[type_image_path]==0.7.5->ydata-profiling->pandas_profiling) (0.2.0)
Requirement already satisfied: networkx>=2.4 in c:\users\imikemori\anaconda3\lib\site-packages (from visions[type_image_path]==0.7.5->ydata-profiling->pandas_profiling) (2.8.4)
Requirement already satisfied: attrs>=19.3.0 in c:\users\imikemori\anaconda3\lib\site-packages (from visions[type_image_path]==0.7.5->ydata-profiling->pandas_profiling) (21.4.0)
Requirement already satisfied: MarkupSafe>=0.23 in c:\users\imikemori\anaconda3\lib\site-packages (from jinja2<3.2,>=2.11.1->ydata-profiling->pandas_profiling) (2.0.1)
Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (1.4.2)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (2.8.2)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (4.25.0)
Requirement already satisfied: pyparsing>=2.2.1 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (3.0.9)
Requirement already satisfied: cycler>=0.10 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (0.11.0)
Requirement already satisfied: packaging>=20.0 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (21.3)
Collecting llvmlite<0.42,>=0.41.0dev0
Using cached llvmlite-0.41.1-cp39-cp39-win_amd64.whl (28.1 MB)
Requirement already satisfied: pytz>=2020.1 in c:\users\imikemori\anaconda3\lib\site-packages (from pandas!=1.4.0,<3,>1.1->ydata-profiling->pandas_profiling) (2022.1)
Requirement already satisfied: joblib>=0.14.1 in c:\users\imikemori\anaconda3\lib\site-packages (from phik<0.13,>=0.11.1->ydata-profiling->pandas_profiling) (1.1.0)
Collecting annotated-types>=0.4.0
Using cached annotated_types-0.6.0-py3-none-any.whl (12 kB)
Requirement already satisfied: typing-extensions>=4.6.1 in c:\users\imikemori\anaconda3\lib\site-packages (from pydantic>=2->ydata-profiling->pandas_profiling) (4.9.0)
Collecting pydantic-core==2.14.6
Using cached pydantic_core-2.14.6-cp39-none-win_amd64.whl (1.9 MB)
Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (2.0.4)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (2022.9.14)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (1.26.11)
Requirement already satisfied: idna<4,>=2.5 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (3.3)
Requirement already satisfied: patsy>=0.5.2 in c:\users\imikemori\anaconda3\lib\site-packages (from statsmodels<1,>=0.13.2->ydata-profiling->pandas_profiling) (0.5.2)
Requirement already satisfied: colorama in c:\users\imikemori\anaconda3\lib\site-packages (from tqdm<5,>=4.48.2->ydata-profiling->pandas_profiling) (0.4.5)
Requirement already satisfied: importlib-metadata>=3.6 in c:\users\imikemori\anaconda3\lib\site-packages (from typeguard<5,>=4.1.2->ydata-profiling->pandas_profiling) (4.11.3)
Requirement already satisfied: zipp>=0.5 in c:\users\imikemori\anaconda3\lib\site-packages (from importlib-metadata>=3.6->typeguard<5,>=4.1.2->ydata-profiling->pandas_profiling) (3.8.0)
Requirement already satisfied: six in c:\users\imikemori\anaconda3\lib\site-packages (from patsy>=0.5.2->statsmodels<1,>=0.13.2->ydata-profiling->pandas_profiling) (1.16.0)
Installing collected packages: pydantic-core, multimethod, llvmlite, dacite, annotated-types, typeguard, pydantic, numba, imagehash, wordcloud, visions, phik, ydata-profiling, pandas_profiling
Attempting uninstall: llvmlite
Found existing installation: llvmlite 0.38.0
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
ERROR: Cannot uninstall 'llvmlite'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
import pandas_profiling
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_13352\1591302161.py in <module>
----> 1 import pandas_profiling
ModuleNotFoundError: No module named 'pandas_profiling'
pip show pandas_profiling
Note: you may need to restart the kernel to use updated packages.
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Package(s) not found: pandas_profiling
pandas_profiling.ProfileReport(dados)
Exemplo: pacote sweetviz#
# Instale se necessário ou se for executar no Google colab
!pip install sweetviz==2.1.1
!pip show sweetviz
#import sweetviz library
import sweetviz as xx
#analisando os dados do banco
study_report = xx.analyze(dados)
# Gerar relatório
study_report.show_html('Dados_banco.html')
Prática#
Visualização e exploração de dados#
Associação entre variáveis#
Considere os dados de 10 mil clientes de um banco no arquivo dados_banco.csv. Estão disponíveis as variáveis:
Cliente: Identificador do cliente.
Sexo: Feminino (F) ou Masculino (M)
Idade: Idade do cliente, em anos completos.
Empresa: Tipo da empresa em que trabalha: Pública, Privada ou Autônomo
Salário: Salário declarado pelo cliente na abertura da conta, em reais.
Saldo_cc: Saldo em conta corrente, em reais.
Saldo_poupança: Saldo em poupança, em reais.
Saldo_investimento: Saldo em investimentos, em reais.
Devedor_cartao: Valor em atraso no cartão de crédito, em reais.
Inadimplente: Se o cliente é considerado inadimplente atualmente (1) ou não (0), de acordo com critérios preestabelecidos.
Analise as possíveis associações entre o sexo, idade, empresa, salário, saldo em conta corrente, saldo em conta poupança, saldo em investimento e devedor no cartão com a variável Inadimplente. Teste outras combinações, por exemplo incluindo a empresa nos gráficos anteriores. Adicione novos elementos utilizando a ajuda das funções. Interprete os resultados.
import os.path
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats
%matplotlib inline
#pkgdir = '/hdd/MBA/ECD/Data'
#Dados banco - Leitura dos dados
#dados = pd.read_csv(f'{pkgdir}/dados_banco.csv', index_col=0)
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
dados.head()
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|---|---|
| Cliente | |||||||||
| 75928 | M | 32 | Privada | 5719.00 | 933.79 | 0.0 | 0.0 | 6023.68 | 0 |
| 52921 | F | 28 | Privada | 5064.00 | 628.37 | 0.0 | 0.0 | 1578.24 | 0 |
| 8387 | F | 24 | Autônomo | 4739.00 | 889.18 | 0.0 | 0.0 | 2578.70 | 0 |
| 54522 | M | 30 | Pública | 5215.00 | 1141.47 | 0.0 | 0.0 | 4348.96 | 0 |
| 45397 | M | 30 | Autônomo | 5215.56 | 520.70 | 0.0 | 0.0 | 1516.78 | 1 |
Associação entre sexo e inadimplência#
# Tabela de dupla entrada
tabela_dupla = pd.crosstab(index=dados['Inadimplente'], columns=dados['Sexo'])
tabela_dupla
| Sexo | F | M |
|---|---|---|
| Inadimplente | ||
| 0 | 3185 | 4354 |
| 1 | 1046 | 1415 |
tabela_dupla/tabela_dupla.sum()
| Sexo | F | M |
|---|---|---|
| Inadimplente | ||
| 0 | 0.752777 | 0.754724 |
| 1 | 0.247223 | 0.245276 |
Associação entre idade e inadimplência#
import seaborn as sns
sns.boxplot(y=dados["Idade"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Idade'>
Associação entre empresa e inadimplência#
# Tabela de dupla entrada
tabela_dupla = pd.crosstab(index=dados['Inadimplente'], columns=dados['Empresa'], margins=True, margins_name='Total')
tabela_dupla
| Empresa | Autônomo | Privada | Pública | Total |
|---|---|---|---|---|
| Inadimplente | ||||
| 0 | 832 | 4579 | 2128 | 7539 |
| 1 | 615 | 1524 | 322 | 2461 |
| Total | 1447 | 6103 | 2450 | 10000 |
tabela_dupla/tabela_dupla.sum()
| Empresa | Autônomo | Privada | Pública | Total |
|---|---|---|---|---|
| Inadimplente | ||||
| 0 | 0.287491 | 0.375143 | 0.434286 | 0.37695 |
| 1 | 0.212509 | 0.124857 | 0.065714 | 0.12305 |
| Total | 0.500000 | 0.500000 | 0.500000 | 0.50000 |
# Tabela de dupla entrada
tabela_dupla = pd.crosstab(index=dados['Inadimplente'], columns=dados['Empresa'], margins=False, margins_name='Total')
tabela_dupla
| Empresa | Autônomo | Privada | Pública |
|---|---|---|---|
| Inadimplente | |||
| 0 | 832 | 4579 | 2128 |
| 1 | 615 | 1524 | 322 |
tabela_dupla.plot.bar(stacked=True)
plt.legend(title='Empresa')
plt.show()
Associação entre salário e inadimplência#
import seaborn as sns
sns.boxplot(y=dados["Salario"],x=dados["Inadimplente"], palette='Set2')
<Axes: xlabel='Inadimplente', ylabel='Salario'>
Associação entre saldo em conta corrente e inadimplência#
import seaborn as sns
sns.boxplot(y=dados["Saldo_cc"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Saldo_cc'>
Associação entre saldo em conta poupança e inadimplência#
import seaborn as sns
sns.boxplot(y=dados["Saldo_poupança"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Saldo_poupança'>
Associação entre devedor no cartão e inadimplência#
import seaborn as sns
sns.boxplot(y=dados["Devedor_cartao"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Devedor_cartao'>
import seaborn as sns
sns.boxplot(y=dados["Devedor_cartao"],x=dados["Inadimplente"], hue=dados["Empresa"], palette="Blues")
<Axes: xlabel='Inadimplente', ylabel='Devedor_cartao'>
import seaborn as sns
sns.catplot(y="Devedor_cartao",x="Inadimplente", hue='Empresa', data=dados, kind='boxen', palette="Set2")
<seaborn.axisgrid.FacetGrid at 0x7f4dad8edc10>
import seaborn as sns
sns.catplot(y="Devedor_cartao",x="Inadimplente", hue='Empresa', data=dados, kind='strip', palette="Set2")
<seaborn.axisgrid.FacetGrid at 0x7f4dad8e6d00>
sns.displot(dados, x='Devedor_cartao', col='Sexo', hue='Inadimplente', bins=30, palette='Set2');
Como não fizemos análises inferenciais, não podemos concluir estatisticamente sobre as associações. Porém, com base nas análises de visualização e exploração de dados, parece existir associação entre saldo em conta corrente e inadimplência e tipo de empresa. A distribuição de dados no devedor de cartão parece ser distinta para os grupos definidos pela variável inadimplente.
Visualizando dados da Covid19
1. Coletando e lendo dados Covid19#
Antes de tudo, instale a biblioteca plotly:
# Para instalar o plotly descomente e execute o comando abaixo
# !pip install plotly
Vamos usar dados do covid19 fornecidos pela “Our World in Data”:
import pandas as pd
import numpy as np
import plotly.express as px
# Conjunto de dados reais da covid19
# Reporitorio dos dados:
# https://github.com/owid/covid-19-data/tree/master/public/data
# https://ourworldindata.org/coronavirus-source-data
df = pd.read_csv('covid-data.csv')
df
| iso_code | continent | location | date | total_cases | new_cases | new_cases_smoothed | total_deaths | new_deaths | new_deaths_smoothed | ... | gdp_per_capita | extreme_poverty | cardiovasc_death_rate | diabetes_prevalence | female_smokers | male_smokers | handwashing_facilities | hospital_beds_per_thousand | life_expectancy | human_development_index | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | AFG | Asia | Afghanistan | 2020-02-24 | 1.0 | 1.0 | NaN | NaN | NaN | NaN | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 1 | AFG | Asia | Afghanistan | 2020-02-25 | 1.0 | 0.0 | NaN | NaN | NaN | NaN | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 2 | AFG | Asia | Afghanistan | 2020-02-26 | 1.0 | 0.0 | NaN | NaN | NaN | NaN | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 3 | AFG | Asia | Afghanistan | 2020-02-27 | 1.0 | 0.0 | NaN | NaN | NaN | NaN | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 4 | AFG | Asia | Afghanistan | 2020-02-28 | 1.0 | 0.0 | NaN | NaN | NaN | NaN | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 82832 | ZWE | Africa | Zimbabwe | 2021-04-15 | 37422.0 | 53.0 | 52.857 | 1550.0 | 2.0 | 2.571 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82833 | ZWE | Africa | Zimbabwe | 2021-04-16 | 37534.0 | 112.0 | 55.286 | 1551.0 | 1.0 | 2.286 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82834 | ZWE | Africa | Zimbabwe | 2021-04-17 | 37699.0 | 165.0 | 60.857 | 1552.0 | 1.0 | 2.000 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82835 | ZWE | Africa | Zimbabwe | 2021-04-18 | 37751.0 | 52.0 | 66.143 | 1553.0 | 1.0 | 2.143 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82836 | ZWE | Africa | Zimbabwe | 2021-04-19 | 37859.0 | 108.0 | 78.857 | 1553.0 | 0.0 | 1.571 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
82837 rows × 59 columns
Não se pode analizar os dados sem entender o que são as variáveis (features/atributos). Temos um codebook pra entender como os ciêntistas codificaram cada variável:
# O que sao cada atributo
# https://github.com/owid/covid-19-data/blob/master/public/data/owid-covid-codebook.csv
df_codebook = pd.read_csv('covid-codebook.csv')
df_codebook.head(10)
| column | source | description | |
|---|---|---|---|
| 0 | iso_code | International Organization for Standardization | ISO 3166-1 alpha-3 – three-letter country codes |
| 1 | continent | Our World in Data | Continent of the geographical location |
| 2 | location | Our World in Data | Geographical location |
| 3 | date | Our World in Data | Date of observation |
| 4 | total_cases | COVID-19 Data Repository by the Center for Sys... | Total confirmed cases of COVID-19 |
| 5 | new_cases | COVID-19 Data Repository by the Center for Sys... | New confirmed cases of COVID-19 |
| 6 | new_cases_smoothed | COVID-19 Data Repository by the Center for Sys... | New confirmed cases of COVID-19 (7-day smoothed) |
| 7 | total_deaths | COVID-19 Data Repository by the Center for Sys... | Total deaths attributed to COVID-19 |
| 8 | new_deaths | COVID-19 Data Repository by the Center for Sys... | New deaths attributed to COVID-19 |
| 9 | new_deaths_smoothed | COVID-19 Data Repository by the Center for Sys... | New deaths attributed to COVID-19 (7-day smoot... |
2. Visualizando dados covid#
Vamos selecionar dados do mês 4 e criar um dataset para explorarmos:
df_aux = df[df['date'].str.startswith('2021-04')]
agg = {
"total_cases": "median", "total_deaths": "median",
"total_cases_per_million": "median", "total_deaths_per_million": "median",
"population": "median", "population_density": "median", "median_age": "median",
"gdp_per_capita": "median", "extreme_poverty": "median",
"human_development_index": "median", "life_expectancy": "median",
"cardiovasc_death_rate": "median", "diabetes_prevalence": "median", "handwashing_facilities": "median",
"people_vaccinated": "median", "people_fully_vaccinated": "median",
"people_vaccinated_per_hundred": "median", "people_vaccinated_per_hundred": "median"
}
df_clean = df_aux.groupby(["iso_code", "continent", "location"]).agg(agg).reset_index()
df_clean
| iso_code | continent | location | total_cases | total_deaths | total_cases_per_million | total_deaths_per_million | population | population_density | median_age | gdp_per_capita | extreme_poverty | human_development_index | life_expectancy | cardiovasc_death_rate | diabetes_prevalence | handwashing_facilities | people_vaccinated | people_fully_vaccinated | people_vaccinated_per_hundred | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | AFG | Asia | Afghanistan | 57144.0 | 2521.0 | 1467.928 | 64.760 | 38928341.0 | 54.422 | 18.6 | 1803.987 | NaN | 0.511 | 64.83 | 597.029 | 9.59 | 37.746 | 120000.0 | NaN | 0.310 |
| 1 | AGO | Africa | Angola | 23331.0 | 550.0 | 709.877 | 16.734 | 32866268.0 | 23.890 | 16.8 | 5819.495 | NaN | 0.581 | 61.15 | 276.045 | 3.94 | 26.664 | 213510.0 | NaN | 0.650 |
| 2 | AIA | North America | Anguilla | NaN | NaN | NaN | NaN | 15002.0 | NaN | NaN | NaN | NaN | NaN | 81.88 | NaN | NaN | NaN | 5835.0 | NaN | 38.890 |
| 3 | ALB | Europe | Albania | 128155.0 | 2310.0 | 44532.282 | 802.697 | 2877800.0 | 104.871 | 38.0 | 11803.431 | 1.1 | 0.795 | 78.57 | 304.195 | 10.08 | NaN | NaN | NaN | NaN |
| 4 | AND | Europe | Andorra | 12497.0 | 120.0 | 161742.057 | 1553.096 | 77265.0 | 163.755 | NaN | NaN | NaN | 0.868 | 83.73 | 109.135 | 7.97 | NaN | 9781.0 | 4484.0 | 12.660 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 202 | WSM | Oceania | Samoa | 3.0 | NaN | 15.120 | NaN | 198410.0 | 69.413 | 22.0 | 6021.557 | NaN | 0.715 | 73.32 | 348.977 | 9.21 | NaN | NaN | NaN | NaN |
| 203 | YEM | Asia | Yemen | 5276.0 | 1031.0 | 176.893 | 34.567 | 29825968.0 | 53.508 | 20.3 | 1479.147 | 18.8 | 0.470 | 66.12 | 495.003 | 5.35 | 49.542 | NaN | NaN | NaN |
| 204 | ZAF | Africa | South Africa | 1557527.0 | 53256.0 | 26261.362 | 897.946 | 59308690.0 | 46.754 | 27.3 | 12294.876 | 18.9 | 0.709 | 64.13 | 200.380 | 5.52 | 43.993 | 285998.5 | 285998.5 | 0.485 |
| 205 | ZMB | Africa | Zambia | 89918.0 | 1226.0 | 4891.113 | 66.689 | 18383956.0 | 22.995 | 17.7 | 3689.251 | 57.5 | 0.584 | 63.89 | 234.499 | 3.94 | 13.938 | 106.0 | NaN | 0.000 |
| 206 | ZWE | Africa | Zimbabwe | 37273.0 | 1538.0 | 2507.783 | 103.479 | 14862927.0 | 42.729 | 19.6 | 1899.775 | 21.4 | 0.571 | 61.49 | 307.846 | 1.82 | 36.791 | 186086.5 | 28382.5 | 1.250 |
207 rows × 20 columns
Como conjunto de dados é real, há muitos valores faltantes. Vamos tratar de forma simples as variáveis que vamos trabalhar:
df_clean = df_clean[df_clean['continent'].notna()]
df_clean = df_clean[df_clean['total_cases'].notna()]
df_clean = df_clean[df_clean['total_cases_per_million'].notna()]
df_clean = df_clean[df_clean['total_deaths_per_million'].notna()]
df_clean["people_fully_vaccinated"] = df_clean["people_fully_vaccinated"].fillna(0.0)
df_clean
| iso_code | continent | location | total_cases | total_deaths | total_cases_per_million | total_deaths_per_million | population | population_density | median_age | gdp_per_capita | extreme_poverty | human_development_index | life_expectancy | cardiovasc_death_rate | diabetes_prevalence | handwashing_facilities | people_vaccinated | people_fully_vaccinated | people_vaccinated_per_hundred | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | AFG | Asia | Afghanistan | 57144.0 | 2521.0 | 1467.928 | 64.760 | 38928341.0 | 54.422 | 18.6 | 1803.987 | NaN | 0.511 | 64.83 | 597.029 | 9.59 | 37.746 | 120000.0 | 0.0 | 0.310 |
| 1 | AGO | Africa | Angola | 23331.0 | 550.0 | 709.877 | 16.734 | 32866268.0 | 23.890 | 16.8 | 5819.495 | NaN | 0.581 | 61.15 | 276.045 | 3.94 | 26.664 | 213510.0 | 0.0 | 0.650 |
| 3 | ALB | Europe | Albania | 128155.0 | 2310.0 | 44532.282 | 802.697 | 2877800.0 | 104.871 | 38.0 | 11803.431 | 1.1 | 0.795 | 78.57 | 304.195 | 10.08 | NaN | NaN | 0.0 | NaN |
| 4 | AND | Europe | Andorra | 12497.0 | 120.0 | 161742.057 | 1553.096 | 77265.0 | 163.755 | NaN | NaN | NaN | 0.868 | 83.73 | 109.135 | 7.97 | NaN | 9781.0 | 4484.0 | 12.660 |
| 5 | ARE | Asia | United Arab Emirates | 481937.0 | 1529.0 | 48727.756 | 154.594 | 9890400.0 | 112.442 | 34.0 | 67293.483 | NaN | 0.890 | 77.97 | 317.840 | 17.26 | NaN | NaN | 0.0 | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 200 | VNM | Asia | Vietnam | 2692.0 | 35.0 | 27.656 | 0.360 | 97338583.0 | 308.127 | 32.6 | 6171.884 | 2.0 | 0.704 | 75.40 | 245.465 | 6.00 | 85.847 | 58248.0 | 0.0 | 0.060 |
| 203 | YEM | Asia | Yemen | 5276.0 | 1031.0 | 176.893 | 34.567 | 29825968.0 | 53.508 | 20.3 | 1479.147 | 18.8 | 0.470 | 66.12 | 495.003 | 5.35 | 49.542 | NaN | 0.0 | NaN |
| 204 | ZAF | Africa | South Africa | 1557527.0 | 53256.0 | 26261.362 | 897.946 | 59308690.0 | 46.754 | 27.3 | 12294.876 | 18.9 | 0.709 | 64.13 | 200.380 | 5.52 | 43.993 | 285998.5 | 285998.5 | 0.485 |
| 205 | ZMB | Africa | Zambia | 89918.0 | 1226.0 | 4891.113 | 66.689 | 18383956.0 | 22.995 | 17.7 | 3689.251 | 57.5 | 0.584 | 63.89 | 234.499 | 3.94 | 13.938 | 106.0 | 0.0 | 0.000 |
| 206 | ZWE | Africa | Zimbabwe | 37273.0 | 1538.0 | 2507.783 | 103.479 | 14862927.0 | 42.729 | 19.6 | 1899.775 | 21.4 | 0.571 | 61.49 | 307.846 | 1.82 | 36.791 | 186086.5 | 28382.5 | 1.250 |
181 rows × 20 columns
Vamos criar um “scatter plot” para visualizar algumas de nossas variáves quantitativas (quantitativa x quantitativa):
fig = px.scatter_matrix(
df_clean,
dimensions=["life_expectancy", "human_development_index", "median_age", "total_deaths_per_million"],
color="total_deaths_per_million",
hover_data=["location"],
# color_continuous_scale=px.colors.sequential.Reds
color_continuous_scale=px.colors.diverging.BrBG
)
fig.show()
Vamos visualizar a distribuição e boxplot da variável “total_deaths_per_million”, por meio do violin:
fig = px.violin(
df_clean, y="total_deaths_per_million",
box=True, # draw box plot inside the violin
hover_data=["location"],
points='all', # can be 'outliers', or False
)
fig.show()
Vamos visualizar violins da variável “life_expectancy” (quantitativa) junto com “continent” (qualitativa):
fig = px.violin(
df_clean, y="life_expectancy", x="continent",
# df_clean, y="population", x="continent",
box=True, # draw box plot inside the violin
hover_data=["location"],
points='all', # can be 'outliers', or False
)
fig.show()
df_clean["continent"].unique()
array(['Asia', 'Africa', 'Europe', 'South America', 'North America',
'Oceania'], dtype=object)
Gráfico de pizza para visualizar “population” (quantitativa) e “continent” (qualitativa):
fig = px.pie(
df_clean,
values='total_cases',
names='continent',
# hover_data=["life_expectancy", "human_development_index", "median_age", "total_deaths_per_million"]
)
fig.show()
Vamos visualizar o total de casos por milhão de habitantes em cada pais/continente:
df_clean["iso_code"]
0 AFG
1 AGO
3 ALB
4 AND
5 ARE
...
200 VNM
203 YEM
204 ZAF
205 ZMB
206 ZWE
Name: iso_code, Length: 181, dtype: object
fig = px.scatter_geo(
df_clean,
locations="iso_code",
color="continent",
hover_name="location",
hover_data=["people_fully_vaccinated", "population"],
size="total_cases_per_million",
scope="world",
projection="natural earth",
size_max=20
)
fig.show()
fig = px.choropleth(
df_clean,
locations="iso_code",
color="total_cases_per_million",
hover_data=["people_fully_vaccinated", "population", "location"],
projection="natural earth",
color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_log10 = df_clean
df_clean_log10["total_cases_per_million_log10"] = np.log10(df_clean_log10["total_cases_per_million"])
fig = px.sunburst(
df_clean_log10,
path=['continent', 'location'],
values='population',
color='total_cases_per_million_log10',
hover_data=['iso_code'],
# color_continuous_scale=px.colors.sequential.Viridis,
color_continuous_scale=px.colors.diverging.BrBG
)
fig.show()
df_clean_sort = df_clean.sort_values("total_cases_per_million", ascending=False)
fig = px.bar(
df_clean_sort,
x='location',
y='total_cases_per_million',
hover_data=['total_cases_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()
Vamos visualizar o total de morte por milhão de habitantes em cada pais/continente:
fig = px.choropleth(
df_clean,
locations="iso_code",
color="total_deaths_per_million",
hover_data=["people_fully_vaccinated", "population"],
projection="natural earth",
color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_log10 = df_clean
df_clean_log10["total_deaths_per_million_log10"] = np.log10(df_clean["total_deaths_per_million"])
fig = px.sunburst(
df_clean_log10,
path=['continent', 'location'],
values='population',
color='total_deaths_per_million_log10',
hover_data=['iso_code'],
# color_continuous_scale=px.colors.sequential.Viridis,
color_continuous_scale=px.colors.diverging.BrBG
)
fig.show()
fig = px.treemap(
df_clean_log10,
path=[px.Constant('world'), 'continent', 'location'],
values='population',
color='total_deaths_per_million_log10', hover_data=['iso_code'],
color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_sort = df_clean.sort_values("total_deaths_per_million", ascending=False)
fig = px.bar(
df_clean_sort,
x='location',
y='total_deaths_per_million',
hover_data=['total_cases_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()
Agora vamos olhar só para a América do Sul e visualizar o total de casos/morte por milhão de habitantes em cada pais:
df_clean_sa = df_clean[df_clean["continent"] == "South America"]
fig = px.choropleth(
df_clean_sa,
locations="iso_code",
color="total_cases_per_million",
hover_name="location",
scope="south america",
color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_sa_sort = df_clean_sa.sort_values("total_cases_per_million", ascending=False)
fig = px.bar(
df_clean_sa_sort,
x='location',
y='total_cases_per_million',
hover_data=['total_cases_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()
fig = px.choropleth(df_clean_sa, locations="iso_code", color="total_deaths_per_million",
hover_name="location", scope="south america",
color_continuous_scale=px.colors.sequential.Reds)
fig.show()
df_clean_sa_sort = df_clean_sa.sort_values("total_deaths_per_million", ascending=False)
fig = px.bar(
df_clean_sa_sort,
x='location',
y='total_deaths_per_million',
hover_data=['total_deaths_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()
3. Visualizando dados covid em multiplas janelas de tempo#
Vamos visualizar no tempo o numero de mortes/casos:
dfm = df
dfm = dfm[dfm['continent'].notna()]
dfm = dfm[dfm['total_cases'].notna()]
dfm = dfm[dfm['total_cases_per_million'].notna()]
dfm = dfm[dfm['total_deaths_per_million'].notna()]
dfm
| iso_code | continent | location | date | total_cases | new_cases | new_cases_smoothed | total_deaths | new_deaths | new_deaths_smoothed | ... | gdp_per_capita | extreme_poverty | cardiovasc_death_rate | diabetes_prevalence | female_smokers | male_smokers | handwashing_facilities | hospital_beds_per_thousand | life_expectancy | human_development_index | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 27 | AFG | Asia | Afghanistan | 2020-03-22 | 34.0 | 4.0 | 2.571 | 1.0 | 1.0 | 0.143 | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 28 | AFG | Asia | Afghanistan | 2020-03-23 | 41.0 | 7.0 | 3.286 | 1.0 | 0.0 | 0.143 | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 29 | AFG | Asia | Afghanistan | 2020-03-24 | 43.0 | 2.0 | 3.286 | 1.0 | 0.0 | 0.143 | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 30 | AFG | Asia | Afghanistan | 2020-03-25 | 76.0 | 33.0 | 7.429 | 2.0 | 1.0 | 0.286 | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| 31 | AFG | Asia | Afghanistan | 2020-03-26 | 80.0 | 4.0 | 7.857 | 3.0 | 1.0 | 0.429 | ... | 1803.987 | NaN | 597.029 | 9.59 | NaN | NaN | 37.746 | 0.5 | 64.83 | 0.511 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 82832 | ZWE | Africa | Zimbabwe | 2021-04-15 | 37422.0 | 53.0 | 52.857 | 1550.0 | 2.0 | 2.571 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82833 | ZWE | Africa | Zimbabwe | 2021-04-16 | 37534.0 | 112.0 | 55.286 | 1551.0 | 1.0 | 2.286 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82834 | ZWE | Africa | Zimbabwe | 2021-04-17 | 37699.0 | 165.0 | 60.857 | 1552.0 | 1.0 | 2.000 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82835 | ZWE | Africa | Zimbabwe | 2021-04-18 | 37751.0 | 52.0 | 66.143 | 1553.0 | 1.0 | 2.143 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
| 82836 | ZWE | Africa | Zimbabwe | 2021-04-19 | 37859.0 | 108.0 | 78.857 | 1553.0 | 0.0 | 1.571 | ... | 1899.775 | 21.4 | 307.846 | 1.82 | 1.6 | 30.7 | 36.791 | 1.7 | 61.49 | 0.571 |
67467 rows × 59 columns
Vamos visualizar “new_cases_per_million”. Para isso inicialmente vamos criar janelas mensais e pegar os valores medianos das variáveis analisadas:
df_ncm = dfm
months = df_ncm['date'].apply(lambda x: x[:7])
df_ncm['months'] = months
df_ncm = df_ncm.groupby(['iso_code', "location", 'months', "continent"]).agg({"new_cases_per_million": "median"})
df_ncm
| new_cases_per_million | ||||
|---|---|---|---|---|
| iso_code | location | months | continent | |
| AFG | Afghanistan | 2020-03 | Asia | 0.2830 |
| 2020-04 | Asia | 1.4390 | ||
| 2020-05 | Asia | 10.4810 | ||
| 2020-06 | Asia | 14.3855 | ||
| 2020-07 | Asia | 3.9560 | ||
| ... | ... | ... | ... | ... |
| ZWE | Zimbabwe | 2020-12 | Africa | 7.5360 |
| 2021-01 | Africa | 42.9930 | ||
| 2021-02 | Africa | 5.0795 | ||
| 2021-03 | Africa | 1.7490 | ||
| 2021-04 | Africa | 2.1530 |
2379 rows × 1 columns
df_ncm = df_ncm.reset_index()
df_ncm = df_ncm[df_ncm["months"] >= "2020-03"] # primeiro mes (2020-01) tem alguns problemas, entao vamos remover
fig = px.scatter_geo(
df_ncm,
locations="iso_code",
color="continent",
hover_name="location",
size="new_cases_per_million",
animation_frame="months",
scope="world",
projection="natural earth",
size_max=30
)
fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 2000
fig.show()
Vamos visualizar “new_deaths_per_million”. Para isso inicialmente vamos criar janelas mensais e pegar os valores medianos das variáveis analisadas:
df_ncm = dfm
months = df_ncm['date'].apply(lambda x: x[:7])
df_ncm['months'] = months
df_ncm = df_ncm.groupby(['iso_code', "location", 'months', "continent"]).agg({"new_deaths_per_million": "median"})
df_ncm
| new_deaths_per_million | ||||
|---|---|---|---|---|
| iso_code | location | months | continent | |
| AFG | Afghanistan | 2020-03 | Asia | 0.0000 |
| 2020-04 | Asia | 0.0385 | ||
| 2020-05 | Asia | 0.1280 | ||
| 2020-06 | Asia | 0.3340 | ||
| 2020-07 | Asia | 0.4370 | ||
| ... | ... | ... | ... | ... |
| ZWE | Zimbabwe | 2020-12 | Africa | 0.1350 |
| 2021-01 | Africa | 1.6150 | ||
| 2021-02 | Africa | 0.5045 | ||
| 2021-03 | Africa | 0.1350 | ||
| 2021-04 | Africa | 0.0670 |
2379 rows × 1 columns
df_ncm = df_ncm.reset_index()
df_ncm = df_ncm[df_ncm["months"] >= "2020-03"] # primeiro mes (2020-01) tem alguns problemas, entao vamos remover
fig = px.scatter_geo(
df_ncm,
locations="iso_code",
color="continent",
hover_name="location",
size="new_deaths_per_million",
animation_frame="months",
scope="world",
projection="natural earth",
size_max=30
)
fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 2000
fig.show()
Para a próxima análise vamos visualizar a variável “new_cases_smoothed_per_million” (média movel semanal). A idéia é contruir gráficos de linhas e visualizar o que aontece ao longo do tempo nos países da América do Sul:
dfm_aux = dfm[dfm["continent"] == "South America"]
dfm_aux = dfm_aux[
["date", "location", "new_deaths_smoothed_per_million", "new_cases_smoothed_per_million"]]
# dfm_aux = dfm_aux.set_index("date")
dfm_aux
| date | location | new_deaths_smoothed_per_million | new_cases_smoothed_per_million | |
|---|---|---|---|---|
| 3041 | 2020-03-08 | Argentina | 0.003 | 0.038 |
| 3042 | 2020-03-09 | Argentina | 0.003 | 0.038 |
| 3043 | 2020-03-10 | Argentina | 0.003 | 0.051 |
| 3044 | 2020-03-11 | Argentina | 0.003 | 0.057 |
| 3045 | 2020-03-12 | Argentina | 0.003 | 0.057 |
| ... | ... | ... | ... | ... |
| 80756 | 2021-04-15 | Venezuela | 0.668 | 40.150 |
| 80757 | 2021-04-16 | Venezuela | 0.658 | 40.934 |
| 80758 | 2021-04-17 | Venezuela | 0.648 | 40.778 |
| 80759 | 2021-04-18 | Venezuela | 0.638 | 41.713 |
| 80760 | 2021-04-19 | Venezuela | 0.653 | 44.124 |
4738 rows × 4 columns
fig = px.line(
dfm_aux,
x = "date",
y = "new_cases_smoothed_per_million",
facet_col="location",
facet_col_wrap=4
)
fig.show()
A mesma coisa, porém para “new_cases_smoothed_per_million” (média movel semanal):
fig = px.line(
dfm_aux,
x = "date",
y = "new_deaths_smoothed_per_million",
facet_col="location",
facet_col_wrap=4
)
fig.show()
Vamos agora visualizar no tempo ambas variáves “new_deaths_smoothed_per_million” e “new_cases_smoothed_per_million”
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02
)
dfm_aux_br = dfm_aux[dfm_aux["location"] == "Brazil"]
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_deaths_smoothed_per_million"],
mode='lines',
name='new_deaths_smoothed_per_million'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_cases_smoothed_per_million"],
mode='lines',
name='new_cases_smoothed_per_million'
),
row=2, col=1
)
fig.update_layout()
fig.show()
4. Visualizando vacinação#
Vamos agora analisar dois paises (Israel e United States) e verificar efeito da vacinação nos numeros de morte e novos casos.
dfm_aux_ = df_aux
s1 = dfm_aux_["people_vaccinated"]/dfm_aux_["population"]
s1.name = "percpop_vaccinated"
dfm_aux_ = pd.concat([dfm_aux_, s1], axis=1)
dfm_aux_ = dfm_aux_.groupby(["location"]).agg({"percpop_vaccinated": "max"}).reset_index()
dfm_aux_.fillna(0.0, inplace=True)
dfm_aux_.sort_values("percpop_vaccinated", ascending=False, inplace=True)
dfm_aux_.head(20)
| location | percpop_vaccinated | |
|---|---|---|
| 76 | Gibraltar | 1.065596 |
| 67 | Falkland Islands | 0.755670 |
| 173 | Seychelles | 0.673683 |
| 23 | Bhutan | 0.620622 |
| 97 | Israel | 0.618871 |
| 96 | Isle of Man | 0.590260 |
| 121 | Maldives | 0.521417 |
| 36 | Cayman Islands | 0.514425 |
| 204 | United Kingdom | 0.485114 |
| 101 | Jersey | 0.471669 |
| 81 | Guernsey | 0.470351 |
| 123 | Malta | 0.433898 |
| 22 | Bermuda | 0.414000 |
| 39 | Chile | 0.404639 |
| 205 | United States | 0.399760 |
| 168 | San Marino | 0.396606 |
| 6 | Anguilla | 0.388948 |
| 15 | Bahrain | 0.360155 |
| 88 | Hungary | 0.343368 |
| 48 | Curacao | 0.336082 |
Análise para “United States”:
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(
rows=5,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02
)
dfm_aux = dfm[dfm["location"] == "United States"]
dfm_aux_br = dfm_aux[
["date", "location", "new_deaths_smoothed_per_million",
"new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
"population", "people_vaccinated", "people_fully_vaccinated"]]
s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"
s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"
dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)
# dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
# dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_deaths_smoothed_per_million"],
mode='lines',
name='new_deaths_smoothed_per_million'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_cases_smoothed_per_million"],
mode='lines',
name='new_cases_smoothed_per_million'
),
row=2, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
mode='lines',
name='new_vaccinations_smoothed_per_million'
),
row=3, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_vaccinated"],
mode='lines',
name='percpop_vaccinated'
),
row=4, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_fully_vaccinated"],
mode='lines',
name='percpop_fully_vaccinated'
),
row=5, col=1
)
fig.update_layout()
fig.show()
Análise para “United Kingdom”:
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(
rows=5,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02
)
dfm_aux = dfm[dfm["location"] == "United Kingdom"]
dfm_aux_br = dfm_aux[
["date", "location", "new_deaths_smoothed_per_million",
"new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
"population", "people_vaccinated", "people_fully_vaccinated"]]
s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"
s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"
dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)
# dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
# dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_deaths_smoothed_per_million"],
mode='lines',
name='new_deaths_smoothed_per_million'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_cases_smoothed_per_million"],
mode='lines',
name='new_cases_smoothed_per_million'
),
row=2, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
mode='lines',
name='new_vaccinations_smoothed_per_million'
),
row=3, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_vaccinated"],
mode='lines',
name='percpop_vaccinated'
),
row=4, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_fully_vaccinated"],
mode='lines',
name='percpop_fully_vaccinated'
),
row=5, col=1
)
fig.update_layout()
fig.show()
Análise para “Israel”:
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(
rows=5,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02
)
dfm_aux = dfm[dfm["location"] == "Israel"]
dfm_aux_br = dfm_aux[
["date", "location", "new_deaths_smoothed_per_million",
"new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
"population", "people_vaccinated", "people_fully_vaccinated"]]
s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"
s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"
dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)
dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_deaths_smoothed_per_million"],
mode='lines',
name='new_deaths_smoothed_per_million'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_cases_smoothed_per_million"],
mode='lines',
name='new_cases_smoothed_per_million'
),
row=2, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
mode='lines',
name='new_vaccinations_smoothed_per_million'
),
row=3, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_vaccinated"],
mode='lines',
name='percpop_vaccinated'
),
row=4, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_fully_vaccinated"],
mode='lines',
name='percpop_fully_vaccinated'
),
row=5, col=1
)
fig.update_layout()
fig.show()
E como estão as coisas no Brasil? :
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(
rows=5,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02
)
dfm_aux = dfm[dfm["location"] == "Brazil"]
dfm_aux_br = dfm_aux[
["date", "location", "new_deaths_smoothed_per_million",
"new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
"population", "people_vaccinated", "people_fully_vaccinated"]]
s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"
s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"
dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)
# dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
# dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_deaths_smoothed_per_million"],
mode='lines',
name='new_deaths_smoothed_per_million'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_cases_smoothed_per_million"],
mode='lines',
name='new_cases_smoothed_per_million'
),
row=2, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
mode='lines',
name='new_vaccinations_smoothed_per_million'
),
row=3, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_vaccinated"],
mode='lines',
name='percpop_vaccinated'
),
row=4, col=1
)
fig.add_trace(
go.Scatter(
x=dfm_aux_br["date"],
y=dfm_aux_br["percpop_fully_vaccinated"],
mode='lines',
name='percpop_fully_vaccinated'
),
row=5, col=1
)
fig.update_layout()
fig.show()
References#
Dong, E., Du, H., & Gardner, L. (2020). An interactive web-based dashboard to track COVID-19 in real time. The Lancet infectious diseases, 20(5), 533-534.
Explore mais sobre Plotly:#
Aula 2: Probabilidades#
Vamos simular alguns problemas que vimos na aula.
Lançamento de uma moeda justa.
import random
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
vp = [] # lista que armazena a fração de ocorrências em função do número de simulações
vsim = [] # armazena o número de simulações
Nmax = 1000 # numero maximo de simulacoes
moeda = ['C','R']
for nsim in np.arange(1,Nmax):
nhead = 0 # numero de caras
for i in range(1,nsim):
face = random.choice(moeda)
if(face == 'C'):
nhead = nhead + 1
vp.append(nhead/nsim)
vsim.append(nsim)
plt.figure(figsize=(8,6))
plt.plot(vsim, vp, linestyle='-', color="blue", linewidth=2,label = 'Valor simulado')
plt.axhline(y=1/2, color='r', linestyle='--', label = 'Valor teorico')
plt.ylabel("Fração de caras", fontsize=20)
plt.xlabel("Numero de experimentos", fontsize=20)
plt.xlim([0.0, Nmax])
plt.ylim([0.0, 1.0])
plt.legend()
plt.show(True)
Pela definição frequentista de probabilidades, vimos que a probabilidade de ocorrência de um evento \(A\) é definida por: $\( P(A) = \lim_{n\rightarrow \infty} \frac{n_A}{n}, \)\( onde \)n$ é o número de experimentos.
Exemplo: Lançando dois dados equilibrados, qual é a probabilidade de que:
a) A soma das faces seja igual a 7 (evento A).
a) Obter uma soma maior do que 5 (evento B).
Na aula, vimos que:
$\(
P(A) = 1/6 = 0.166
\)\(
\)\(
P(B) = 26/36 = 0.72.
\)$
import random
import numpy as np
n = 1000 #numero de experimentos
nA = 0
nB = 0
faces = np.arange(1,7) #valores 1 a 6
for i in range(0,n):
dado1 = random.choice(faces)
dado2 = random.choice(faces)
if (dado1+dado2 == 7):
nA = nA + 1
if((dado1 + dado2) > 5):
nB = nB + 1
nA = nA/n
nB = nB/n
print('Probabilidade de que a soma das faces seja igual a 7:', nA)
print('Probabilidade de que a soma das faces seja maior do que 5:', nB)
Probabilidade de que a soma das faces seja igual a 7: 0.162
Probabilidade de que a soma das faces seja maior do que 5: 0.709
Exemplo: Qual é a probabilidade de que em um lançamento de dados saia um número par ou maior do que três?
Solução: 4/6 = 0.66.
import random
import numpy as np
n = 1000 #numero de experimentos
nA = 0
faces = np.arange(1,7) #valores 1 a 6
for i in range(0,n):
dado = random.choice(faces)
if (dado%2 == 0) or (dado > 3):
nA = nA + 1
nA = nA/n
print('Probabilidade de que saia um número par ou maior do que três:', nA)
Probabilidade de que saia um número par ou maior do que três: 0.648
Exemplo: Uma caixa usada em um sorteio contém 5 bolas pretas numeradas de 1 a 5, sete bolas brancas numeradas de 1 a 7 e oito bolas vermelhas numeradas de 1 a 8.
i) Sorteando-se uma bola dessa caixa, qual é a probabilidade de encontrarmos uma bola preta?
$\(
P(A) = \frac{5}{20} = 0.25
\)$
import random
urna = ['P1','P2','P3','P4','P5','B1','B2','B3','B4','B5','B6','B7',
'V1','V2','V3','V4','V5','V6','V7','V8']
n = 1000
nA = 0
for i in range(0,N):
bola = random.choice(urna)
if(bola[0][0] == 'P'):
nA = nA + 1
print('P(A):', nA/n)
P(A): 0.262
ii) Sorteando-se uma bola preta, qual é a probabilidade de tal bola ser par? $\( P(B|A) = \frac{2/20}{5/20} = \frac{2}{5}= 0.4 \)$
import random
urna = ['P1','P2','P3','P4','P5','B1','B2','B3','B4','B5','B6','B7',
'V1','V2','V3','V4','V5','V6','V7','V8']
n = 1000
nA = 0
nB = 0
for i in range(0,N):
bola = random.choice(urna)
if(bola[0] == 'P'):
nA = nA + 1
if(int(bola[1])%2 == 0):
nB = nB + 1
print('P(B|A):', (nB/n)/(nA/n))
P(B|A): 0.41129032258064513
Exercícios de fixação#
Resolva e simule os exercícios a seguir.
1 - Lançando dois dados equilibrados, qual é a probabilidade de que:
a) A soma das faces seja igual a 6 (evento A).
a) Obter uma soma maior do que 10 (evento B).
2 - Qual é a probabilidade de que em um lançamento de dados saia um número ímpar ou maior do que dois?
3 - Uma caixa usada em um sorteio contém 2 bolas pretas numeradas de 1 a 2, três bolas brancas numeradas de 1 a 3 e cinco bolas vermelhas numeradas de 1 a 5.
a) Qual é a probabilidade de que uma bola sorteada seja branca ou par?
b) Dado que uma bola é vermelha, qual é a chance ser ímpar?
4 - Simule o exercício resolvido na aula teórica e compare os resultados: Lançamos dois dados e observamos a variável aleatória X é igual a 1 se a soma for par ou igual a zero, caso contrário. Determine a distribuição de X.
Aula 3: Modelos Probabilísticos#
Valor esperado#
O valor esperado pode ser aproximado pela média de uma variável aleatória, fato jutificado pela Lei dos Grandes Números, que veremos nas próximas aulas.
Exemplo: Seja a variável aleatória X com distribuição abaixo. Calcule E[X] e V(X). $\( P(X=0) = 0.2,\quad P(X=1) = 0.2, \quad P(X = 2) = 0.6 \)\( O valor esperado: \)\( E[X] = 0*0.2 + 1*0.2 + 2*0.6 = 1.4 \)\( \)\( V(X) = E[X^2]-E[X]^2 = 0.64 \)$
import random
import numpy as np
n = 1000 #numero de experimentos
nA = 0
nB = 0
X = [0,0,1,1,2,2,2,2,2]
x_obs = []
for i in range(0,n):
x_obs.append(random.choice(X))
print('Valor esperado de X:', np.mean(x_obs))
print('Variância de X:', np.std(x_obs)**2)
Valor esperado de X: 1.317
Variância de X: 0.6765109999999999
Lançamento de uma moeda#
Vamos simular o lançamento de uma moeda nsim vezes e comparar o valor \(p\) (probabilidade sair cara) com o valor obtido através de simulações.
import numpy as np
p = 0.6 # probabilidade de sair cara
nsim = 10 # num de experimentos
nhead = 0 # num de caras obtidas
saida = [] # armazena as saidas (cara:1, coroa:0)
for i in range(0, nsim):
if(np.random.uniform() < p): # se menor que p, cara
nhead = nhead + 1 # incrementa o contador de caras
saida.append(1)
else:
saida.append(0)
print("Saida:", saida)
print("Frequencia teórica: p= ",p)
print("Frequencia de caras:", nhead/nsim)
Saida: [1, 1, 1, 1, 1, 0, 1, 1, 0, 0]
Frequencia teórica: p= 0.6
Frequencia de caras: 0.7
Distribuição binomial#
O lançamento de uma moeda \(n\) vezes é um processo de Bernoulli, pois há apenas duas saídas possíveis e os experimentos são independentes. Nesse caso, a probabilidade de sair \(k\) sucessos em \(n\) experimentos é calculada pela distribuição Binomial.
Vamos considerar o lançamento de uma moeda n vezes e calcular a distribuição teórica e a distribuição de probabilidade associada ao experimento.
from random import seed
from matplotlib import pyplot as plt
import numpy as np
from scipy.stats import binom
import math
seed(100) # semente do gerador de números aleatórios
n = 100 # numero de lançamentos
p = 0.3 # probabilidade de sair cara
Pk = np.zeros(n)
vk = np.arange(0,n)
ns = 1000 # numero de simulacoes
for j in range(0,ns): # faça para ns simulacoes
S = 0 # numero de sucessos
for i in range(0,n): # faça para n experimentos
r = np.random.uniform() #
if(r <= p): # se o sucesso
S = S + 1
Pk[S] = Pk[S] + 1
Pk=Pk/sum(Pk) # normaliza a distribuição de probabilidade
plt.figure(figsize=(10,6))
plt.xlim(0.8*np.min(vk[Pk>0]),1.2*np.max(vk[Pk>0]))
plt.bar(vk, Pk, label='Simulacao')
# curva teórica
Pkt = np.zeros(n+1) # valores teóricos da probabilidade
vkt = np.arange(0,n+1) # variação em k
for k in range(0,n+1): # varia de 0 até n
C = (math.factorial(n)/(math.factorial(n-k)*math.factorial(k)))
Pkt[k] = C*(p**k)*(1-p)**(n-k)
plt.plot(vkt, Pkt, 'r--', label='Prob. Teórica')
plt.xlabel('k', fontsize = 20)
plt.ylabel('P(k)',fontsize = 20)
plt.legend(fontsize = 15)
plt.show(True)
Repetindo a análise usando funções do Python:
from scipy.stats import binom
seed(100) # semente do gerador de números aleatórios
n = 100 # numero de lançamentos
p = 0.3 # probabilidade de sair cara
ns = 1000 # numero de simulacoes
X = np.random.binomial(n, p, ns) # funcao para gerar valores de uma binomial
plt.figure(figsize=(10,6))
Pk, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7,
rwidth=0.9)
# curva teórica
Pkt = np.zeros(n+1) # valores teóricos da probabilidade
vkt = np.arange(0,n+1) # variação em k
for k in range(0,n+1): # varia de 0 até n
C = (math.factorial(n)/(math.factorial(n-k)*math.factorial(k)))
Pkt[k] = C*(p**k)*(1-p)**(n-k)
plt.plot(vkt, Pkt, 'r--', label='Prob. Teórica')
plt.xlabel('k', fontsize = 20)
plt.ylabel('P(k)',fontsize = 20)
plt.legend(fontsize = 15)
plt.xlim(10,50)
plt.show(True)
Exemplo: Em uma urna há 8 bolas brancas e 4 pretas. Retira-se 5 bolas com reposição. Calcule a probabilidade de que:
a) saiam duas bolas brancas.
Vamos construir uma função para calcular o valor exato.
import math
def binomial(n,p,k):
C = (math.factorial(n)/(math.factorial(n-k)*math.factorial(k)))
pk = C*(p**k)*(1-p)**(n-k)
return pk
O valor teórico:
n = 5
p = 8/12
k = 2
print('Probabilidade:', binomial(n,p,k))
Probabilidade: 0.16460905349794244
from scipy.stats import binom
#binom.pmf(k) = choose(n, k) * p**k * (1-p)**(n-k)
ns = 1000 #numero de experimentos
X = ['B','B','B','B','B','B','B','B','P','P','P','P']
n = 5 # numero de bolas retiradas
k = 0
for i in range(0,ns):
saida = []
for j in range(0,n):
bola = random.choice(X)
saida.append(bola)
nbrancas = 0
for s in saida:
if(s == 'B'):
nbrancas = nbrancas + 1
if(nbrancas == 2):
k = k + 1 # se sair branca, temos mais um sucesso
print('Valor teórico:', binom.pmf(2, 5, 8/12))
print('Valor obtido = ', k/ns)
Valor teórico: 0.16460905349794241
Valor obtido = 0.163
b) saiam ao menos 3 pretas.
Valor teórico.
n = 5
p = 4/12
k = 2
pk = 0
for k in range(3,6):
pk = pk + binomial(n,p,k)
print('Valor teórico:', pk)
Valor teórico: 0.20987654320987656
from scipy.stats import binom
#binom.pmf(k) = choose(n, k) * p**k * (1-p)**(n-k)
ns = 1000 #numero de experimentos
X = ['B','B','B','B','B','B','B','B','P','P','P','P']
n = 5 # numero de bolas retiradas
k = 0
for i in range(0,ns):
saida = []
for j in range(0,n):
bola = random.choice(X)
saida.append(bola)
npretas = 0
for s in saida:
if(s == 'P'):
npretas = npretas + 1
if(npretas >= 3):
k = k + 1
print('Valor teórico:', pk)
print('Valor obtido = ', k/ns)
Valor teórico: 0.20987654320987656
Valor obtido = 0.207
Modelo de Poisson#
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import factorial
lbd = 50 # taxa
ns = 1000 # numero de pontos extraídos de uma distribuição de Poisson
X = np.random.poisson(lam=lbd, size=ns)
plt.figure(figsize=(10,6))
P, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7,
rwidth=0.9)
plt.xlabel('k', fontsize = 15)
plt.ylabel('P(k)',fontsize = 15)
plt.show(True)
Exemplo: Em uma central telefônica, chegam 300 mensagens por hora. Qual é a probabilidade de que:
import numpy as np
import math
def Poisson(lbd, k):
pk = np.exp(-lbd)*(lbd**k)/math.factorial(k)
return pk
a) Em um minuto não ocorra nenhuma chamada.
lbd = 5 #numero de chamadas por minuto
k = 0
print("P(k = 0) = ",Poisson(lbd,k))
P(k = 0) = 0.006737946999085467
b) Em dois minutos ocorram duas chamadas.
lbd = 10 #numero de chamadas por 2 minutos
k = 2
print("P(k = 2) = ",Poisson(lbd,k))
P(k = 2) = 0.0022699964881242427
Modelo exponencial#
from scipy.stats import expon
import matplotlib.pyplot as plt
import numpy as np
alpha = 2
X = expon.rvs(scale=1/alpha,size=1000)
plt.figure(figsize=(8,5))
P, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7,
rwidth=0.9)
plt.xlabel('k', fontsize = 15)
plt.ylabel('P(k)',fontsize = 15)
plt.show(True)
print('Esperanca teorica:', 1/alpha, 'Média amostral:', np.mean(X))
print('Variância teórica:', 1/alpha**2,'Variância amostral:', np.var(X))
Esperanca teorica: 0.5 Média amostral: 0.49408903956981065
Variância teórica: 0.25 Variância amostral: 0.2472047214966368
Exercícios de fixação#
1 - Seja a variável aleatória X com distribuição abaixo. Calcule E[X]. $\( P(X=0) = 0.4,\quad P(X=1) = 0.4, \quad P(X = 2) = 0.2 \)$ Calcule o valor esperado e simule o problema como feito anteriormente.
2 - Em uma urna há 5 bolas brancas e 9 pretas. Retira-se 5 bolas com reposição. Calcule a probabilidade de que:
a) saiam 3 bolas brancas.
b) saiam ao menos 5 pretas.
3 - Gere dados com distribuição de Poisson com \(\lambda = 5\). Verifique se \(E[X]=V[X]=\lambda\) usando simulação. Faça isso para diferences valores de \(\lambda\).
4 - Gere dados com distribuição exponencial com \(\alpha = 2\). Verifique se \(E[X] = 1/\alpha\) e \(=V[X]=1/\alpha^2\) usando simulação.
Distribuição Normal e Teoremas Limites#
Nessa aula, vamos estudar a distribuição normal. Vamos inicialmente gerar dados a partir dessa distribuição.
import numpy as np
import matplotlib.pyplot as plt
import math as math
# funcao que mostra a distribuicao teorica
def normal_dist(x , mean , sigma):
prob_density = (1/(sigma*(math.sqrt(2*np.pi))))*np.exp(-0.5*((x-mean)/sigma)**2)
return prob_density
#Calculate mean and Standard deviation.
mean = 75
sigma = 20
n = 10000
X = np.random.normal(mean, sigma, n)
plt.figure(figsize=(10,6))
Pk, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7,
rwidth=0.9)
# define os valores de x
x = np.linspace(np.min(X),np.max(X),200)
# Distribuicao teorica
pdf = normal_dist(x,mean,sigma)
#Plotting the Results
plt.plot(x,pdf , color = 'red')
plt.xlabel('Data points')
plt.ylabel('Probability Density')
Text(0, 0.5, 'Probability Density')
Podemos resolver alguns exemplos para verificar algumas aplicações dessa distribuição.
Exemplo: Se \(X \sim \mathcal{N}(\mu=165,\,\sigma^{2}=9)\), calcule \(P(X<162)\).
import scipy.stats as st
media = 165
dp = 3
z = (162-media)/dp
print('Z:', z)
print(st.norm.cdf(z))
Z: -1.0
0.15865525393145707
Exemplo: Se \(X \sim \mathcal{N}(\mu=10,\,\sigma^{2}=4)\), calcule \(P(X>13)\).
import scipy.stats as st
media = 10
dp = 2
z = (13-media)/dp
print('Z:', z)
print(1-st.norm.cdf(z))
Z: 1.5
0.06680720126885809
Exemplo: O peso médio de 500 estudantes do sexo masculino de uma determinada universidade é 75,5 Kg e o desvio padrão é 7,5 Kg. Admitindo que os pesos são normalmente distribuídos, determine a percentagem de estudantes que pesam:
a) entre 60 e 77,5 Kg. $\( P(60 \leq X \leq 77,5) = P\left(\frac{60-\mu}{\sigma} \leq \frac{X-\mu}{\sigma} \leq \frac{77,5-\mu}{\sigma}\right)=P\left(\frac{60-\mu}{\sigma} \leq Z \leq \frac{77,5-\mu}{\sigma}\right) = \)\( \)\( = P\left(Z \leq \frac{77,5-\mu}{\sigma}\right)-P\left( Z \leq \frac{60-\mu}{\sigma}\right) \)$
import scipy.stats as st
media = 75.5
dp = 7.5
z1 = (60-media)/dp
z2 = (77.5-media)/dp
print('Probabilidade teórica:',st.norm.cdf(z2)-st.norm.cdf(z1))
Probabilidade teórica: 0.5857543024471563
Simulando:
media = 75.5
dp = 7.5
n = 100
X = np.random.normal(media, dp, n)
m = 0
for x in X:
if x > 60 and x < 77.5:
m = m + 1
print('Probabilidade (simulação):', m/n)
Probabilidade (simulação): 0.59
b) mais do que 92,5 Kg. $\( P(X \geq 92,5) = P\left( \frac{X-\mu}{\sigma}\geq \frac{92,5-\mu}{\sigma}\right) = P\left( Z \geq \frac{92,5-\mu}{\sigma}\right) = 1 - P\left( Z < \frac{92,5-\mu}{\sigma}\right) \)$
z1 = (92.5-media)/dp
p = 1-st.norm.cdf(z1)
print('Probabilidade teórica:',p)
Probabilidade teórica: 0.011705298080558313
media = 75.5
dp = 7.5
n = 100
X = np.random.normal(media, dp, n)
m = 0
for x in X:
if x > 92.5:
m = m + 1
print('Probabilidade (simulação):', m/n)
Probabilidade (simulação): 0.02
Exemplo: Uma máquina de bebidas está regulada de modo a servir uma média de 150ml por copo. Se a quantidade servida por copo seguir uma distribuição normal com desvio padrão de 20 ml, determine a percentagem de copos que conterão mais de 175ml de bebida.
media = 150
dp = 20
z = (175-media)/dp
print((1-st.norm.cdf(z))*100,'%')
10.564977366685536 %
Teorema Central do Limite#
A distribuição Normal aparece no Teorema Central do Limite.
Teorema: Seja uma amostra aleatória \((X_1,X_2,\ldots,X_n)\) retiradas de uma população com média \(\mu\) e variância \(\sigma\). A distribuição amostral de \(\bar{X}\) aproxima-se, para n grande, de uma distribuição normal com média \(E[\bar{X}]=\mu\) e variância \(\sigma^2/n\).
import scipy.stats as stats
vS = [1, 2 , 4 , 8, 50, 100, 1000]# sample size
S = 500 # number of samples
mu = 2
for n in vS: #sample size
vmean = []
for s in range(0,S): # select s samples of size n
X = np.random.uniform(0,2*mu, n) # X is generated from a uniform probability distribution
#X = np.random.exponential(mu, n) # X is generated from an exponential probability distribution
vmean.append(np.mean(X))
plt.figure(figsize=(6,4))
plt.hist(x=vmean, bins='auto', color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
# Plot the theoretical curve
#xt = plt.xticks()[0]
xmin, xmax = min(vmean), max(vmean)
lnspc = np.linspace(xmin, xmax, len(vmean))
m, s = stats.norm.fit(vmean) # get mean and standard deviation
pdf_g = stats.norm.pdf(lnspc, m, s) # now get theoretical values in our interval
plt.plot(lnspc, pdf_g, label="Norm") # plot it
plt.show(True)
Vemos que independentemente da distribuição de \(X\), a distribuição da média amostral sempre segue uma normal quando o número de amostras é grande.
Vamos considerar alguns exemplos.
Exemplo: Seja a variável aleatória com distribuição de probabilidade: P(X=3)=0,4; P(X=6)=0,3; P(X=8)=0,3. Uma amostra com 40 observações é sorteada. Qual é a probabilidade de que a média amostral ser maior do que 5?
def esperanca(X,P):
E = 0
for i in range(0, len(X)):
E = E + X[i]*P[i]
return E
def variancia(X,P):
E = 0; E2 = 0
for i in range(0, len(X)):
E = E + X[i]*P[i]
E2 = E2 + (X[i]**2)*P[i]
V = E2-E**2
return V
X = [3,6,8]
P = [0.4,0.3,0.3]
E = esperanca(X,P)
V = variancia(X,P)
print("Esperança:", E, "Variância:",V)
Esperança: 5.4 Variância: 4.439999999999991
Valor teórico:
import scipy.stats as st
import numpy as np
mu = E
sigma = np.sqrt(V)
n = 40
x = 5
Z = (x - mu)/(sigma/np.sqrt(n))
pt = 1-st.norm.cdf(Z)
print('Probabilidade:',pt)
Probabilidade: 0.885046886863795
Vamos sortear várias amostras de tamanho n=40 e verificar qual a probabilidade da média dessa amostra ser maior do que 5.
import matplotlib.pyplot as plt
n = 40
ns = 1000 #numero de simulacoes
vx = [] # armazena a media amostral
for s in range(0,ns):
A = np.random.choice(X, n, p=P)
vx.append(np.mean(A))
plt.figure(figsize=(8,6))
plt.hist(x=vx, bins='auto',color='#0504aa', alpha=0.7, rwidth=0.85, density = True)
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)
print("Media das amostras:", np.mean(vx), "Media da população:", E)
Media das amostras: 5.408875 Media da população: 5.4
A probabilidade de ser maior do que 5:
nmaior = 0
for i in range(0, len(vx)):
if(vx[i] > 5):
nmaior = nmaior + 1
nmaior = nmaior/len(vx)
print("Probabilidade de ser maior do que 5:", nmaior, "Valor teórico:", pt)
Probabilidade de ser maior do que 5: 0.901 Valor teórico: 0.885046886863795
Lei dos grandes números#
Outro teorema fundamental e a lei dos grandes números. Basicamente, a lei forte dos grandes números é enunciada da seguinte forma:
Sejam \(X_1,X_2,\ldots, X_n\) um conjunto de variáveis aleatórias independentes e identicamente distribuídas. Seja \(E[X_i]=\mu\). Então, com probabilidade um, podemos afirmar: $\( \lim_{n \rightarrow \infty} \frac{X_1+X_2+\ldots+X_n}{n} \rightarrow \mu \)$
Podemos verificar essa lei através de simulações.
Vamos considerar o lançamento de uma moeda. Seja \(p\) a chance de sair cara. Então \(E[X)i]=p\), sendo \(i\) o i-ésimo lançamento da moeda e \(X=1\) se for cara ou \(X=0\), se sair coroa. Conforme notamos a seguir, quando aumentamos o número de simulações, a média de \(n\) simulações converge para a probabilidade \(p\).
import numpy as np
import matplotlib.pyplot as plt
p = 0.7
vp = [] # lista que armazena a fração de ocorrências em função do número de simulações nsim
vsim = [] # armazena o número de simulações
Nmax = 1000 # numero maximo de simulacoes
for nsim in np.arange(1,Nmax,1):
nhead = 0 # numero de caras
for i in range(1,nsim):
if(np.random.uniform() < p):
nhead = nhead + 1
vp.append(nhead/nsim)
vsim.append(nsim)
plt.figure(figsize=(8,6))
plt.plot(vsim, vp, linestyle='-', color="blue", linewidth=2,label = 'Valor simulado')
plt.axhline(y=p, color='r', linestyle='--', label = 'Valor teorico')
plt.ylabel("Fração de caras", fontsize=20)
plt.xlabel("Numero de experimentos", fontsize=20)
plt.xlim([0.0, Nmax])
plt.ylim([0.0, 1.0])
plt.legend()
plt.show(True)
Exercícios de fixação#
1 - Se \(X \sim \mathcal{N}(\mu=100,\,\sigma^{2}=10)\), calcule \(P(X<95)\).
2 - O peso médio de n estudantes do sexo masculino de uma determinada universidade é 75,5 Kg e o desvio padrão é 7,5 Kg. Admitindo que os pesos são normalmente distribuídos, determine a percentagem de estudantes que pesam entre 60 e 80 Kg. Considere n=100, 200 e 500. Compare os resultados.
3 - Obtenha os valor simulador (como feito anteriormente), para o problema a seguir. Compare a simulação com o valor teórico.
Uma máquina de bebidas está regulada de modo a servir uma média de 150ml por copo. Se a quantidade servida por copo seguir uma distribuição normal com desvio padrão de 20 ml, determine a percentagem de copos que conterão mais de 175ml de bebida.
4 - Verifique a lei dos grandes números para diferentes valores de \(p\) no exemplo anterior.
Teorema Central do Limite#
Teorema: Seja uma amostra aleatória \((X_1,X_2,\ldots,X_n)\) retiradas de uma população com média \(\mu\) e variância \(\sigma\). A distribuição amostral de \(\bar{X}\) aproxima-se, para \(n\) grande, de uma distribuição normal com média \(E[\bar{X}]=\mu\) e variância \(\sigma^2/n\).
Lei dos grandes números#
Outro teorema fundamental e a lei dos grandes números. Basicamente, a lei forte dos grandes números é enunciada da seguinte forma:
Sejam \(X_1,X_2,\ldots, X_n\) um conjunto de variáveis aleatórias independentes e identicamente distribuídas. Seja \(E[X_i]=\mu\). Então, com probabilidade um, podemos afirmar: $\( \lim_{n \rightarrow \infty} \frac{X_1+X_2+\ldots+X_n}{n} \rightarrow \mu \)$
Exercícios#
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st # para uso de st.norm.cdf(Z)
Exercício 1:#
O tempo para desenvolver um servidor web em uma empresa é descrito por uma variável aleatória X, medida em dias, com distribuição normal de média 𝜇 = 45 e variância \(𝜎^2 = 400\). Calcule a probabilidade de que um novo servidor web será finalizado entre 40 e 45 dias.
\(X\): dias gastos pela empresa para confeccionar o software
\(\sigma^2: 400 \Rightarrow \sigma = 20\)
\(X \sim \mathcal{N}(\mu = 45, \sigma = 20)\)
\(P(40 < X < 45) = P\left(\frac{40 - \mu}{\sigma} < \frac{X - \mu}{\sigma} < \frac{45 - \mu}{\sigma}\right) = P\left(\frac{40 - 45}{20} < Z < \frac{45 - 45}{20}\right) = P\left(-0.25 < Z < 0\right)\)
\(P\left(-0.25 < Z < 0\right) = P(Z < 0) - P(Z < -0.25) = 0.5 - 0.4013 = 0.0987 \approx 9.87\%\)
A probabilidade de que o servidor fique prono entre 40 e 45 dias é de aproximadamente \(9.87\%\).
0.5 - 0.4013
0.09870000000000001
Exercício 2:#
Uma população é descrita pela seguinte distribuição de probabilidades:
Uma amostra com 50 observações é sorteada. Calcule a probabilidade de que a média dessa amostra seja maior do que 5.
\(X = \{2, 4, 6\}\)
\(P(X = 2) = 0.2; P(X = 4) = 0.4; P(X = 6) = 0.4\)
\(E(X) = 2\cdot 0.2 + 4\cdot 0.4 + 6\cdot 0.4 = 4.4\)
\(V(X) = E(X^2) - (E(X))^2 =\\= [2^2\cdot P(Z = 2^2) + 4^2\cdot P(Z = 4^2) + 6^2\cdot P(Z = 6^2)] - (4.4)^2 = [2^2\cdot 0.2 + 4^2\cdot 0.4 + 6^2\cdot 0.4] - (4.4)^2 = 21.6 - 19.36 = 2.24\)
\(Z = X^2 = \{4, 16, 36\}\)
\(\sigma = \sqrt{V(X)} = \sqrt{2.24} = 1.49\)
\(X \sim \mathcal{N}(4.4, \sigma / \sqrt{50}) = \mathcal{N}(4.4, 0.21)\)
\(P(\bar{X} > 5) = ?\)
\(P\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}} > \frac{5 - \mu}{\sigma/\sqrt{n}}\right) = P\left(Z > \frac{5 - 4.4}{0.21}\right) = P\left(Z > 2.847\right) =\\= 1 - P(Z < 2.847) = 1 - 0.9977 = 0.0022 \approx 0.22\%\)
1 - st.norm.cdf(2.847)
0.002206668118837518
1.49 / np.sqrt(50), (5-4.4)/ (1.49 / np.sqrt(50))
(0.21071782079359117, 2.8474098571270354)
2*0.2+4*0.4+6*0.4, 2**2 * 0.2 + 4**2 * 0.4 + 6**2 * 0.4, 4.4**2, 21.6 - 19.36, np.sqrt(2.24)
(4.4, 21.6, 19.360000000000003, 2.240000000000002, 1.4966629547095767)
Simulação
X = [2,4,6]
P = [0.2,0.4,0.4]
ns = 100000 #numero de simulacoes
n = 50 # Número de amostras geradas
vx = [] # armazena a media amostral
for s in range(0,ns):
A = np.random.choice(X, n, p=P) # Roleta viciada
vx.append(np.mean(A))
plt.hist(vx)
(array([3.3000e+01, 3.5200e+02, 3.4530e+03, 1.1059e+04, 2.3700e+04,
3.5800e+04, 1.7835e+04, 6.9950e+03, 7.1200e+02, 6.1000e+01]),
array([3.48 , 3.656, 3.832, 4.008, 4.184, 4.36 , 4.536, 4.712, 4.888,
5.064, 5.24 ]),
<BarContainer object of 10 artists>)
Repare que, em 10000 simulações de sorteio de 50 amostras de X de acordo com as probabilidades P, foram muito raras as ocasiões nas quais a média das 50 amostras superou 5. Especificamente,
n_maiorque5 = len([i for i in vx if i >= 5])
print('Em exatas ', n_maiorque5, 'simulações a média de 50 amostras superou 5, correspondendo a ', 100 * n_maiorque5 / len(vx),'% das simulações.')
print('Em outras palavras, em ', 100 * (1-n_maiorque5 / len(vx)),'% dos casos simulados, a média das 50 amostras geradas não superou o valor 5.')
Em exatas 226 simulações a média de 50 amostras superou 5, correspondendo a 0.226 % das simulações.
Em outras palavras, em 99.774 % dos casos simulados, a média das 50 amostras geradas não superou o valor 5.
Exercício 3:#
Em uma empresa de venda de softwares, a duração de conversas telefônicas (em minutos) segue o modelo exponencial com parâmetro 𝜆 = 1/5. Observando-se uma amostra aleatória de 50 dessas chamadas, qual será a probabilidade de que tais amostras em média não ultrapassem 6 minutos?
\( X \sim \lambda = 1/5 \)
\(E(X) = \frac{1}{\lambda} = \frac{1}{1/5} = 5\)
\(V(X) = \frac{1}{\lambda^2} = \frac{1}{\frac{1}{25}} = 25\)
\( \bar{X} \sim \mathcal{N}(5, 25)\)
\(P\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}} < \frac{6 - \mu}{\sigma/\sqrt{n}}\right) = P(Z < 1.414) = 0.9207 \approx 92.07\%\)
(6 - 5) / (5 / np.sqrt(50))
1.4142135623730951
Exercício 4:#
Qual deve ser o tamanho de uma amostra a ser retirada de uma população \(𝑋\sim \mathcal{N}(𝜇 = 100, 𝜎 = 50)\) para que \(𝑃(90 < 𝑋̅ < 110) = 0.95\)?
Temos que \(P(Z < -z) = \frac{(1-A)}{2}\), sendo \(A\) a probabilidade exigida.
\(P\left(\frac{90-100}{50/\sqrt{n}} < Z < \frac{110-100}{50/\sqrt{n}}\right) = P\left(\frac{-10}{50/\sqrt{n}} < Z < \frac{10}{50/\sqrt{n}}\right) = P(-z < Z < z) = A\)
Uma vez que sabemos o valor de \(A\), basta agora calcularmos o valor de \(P(Z < -z)\) e, na sequência, conferirmos o valor de \(z\) na Tabela Padronizada.
\(P(Z < -z) = \frac{(1-A)}{2} = \frac{(1-0.95)}{2} = 0.025\)
Sabemos que \(-z = -1.96 \Rightarrow z = 1.96\)
Substituindo,
\(z = \frac{10}{50/\sqrt{n}} = \frac{\sqrt{n}}{5}\)
Então,
\(\frac{\sqrt{n}}{5} = 1.96 \Rightarrow\)
\(\frac{n}{25} = 1.96^2 \Rightarrow n = 25\cdot 1.96^2 = 96\)
O tamanho da amostra deve ser de, no mínimo, 96 elementos.
25*1.96*1.96
96.03999999999999
Propriedades: Considere \(X\) e \(Y\) variáveis aleatórias independentes e identicamente distribuídas (i.i.d.). Considere também \(\alpha, \beta, \gamma \in \mathbb{R}\). Então:
\(E(\alpha X + \beta Y + \gamma) = \alpha E(X) + \beta E (Y) + \gamma\);
\(V(\alpha X + \beta Y + \gamma) = \alpha^2 V(X) + \beta^2 V(Y)\).
Exercício 5:#
Seja \([𝑋_1, 𝑋_2, … , 𝑋_𝑛]\) uma amostra aleatória de uma população com média \(\mu\) e variância \(\sigma^2/n\). Mostre que para a estatística $\(𝑍 = \frac{(𝑋̅ − 𝜇)}{\left(\frac{𝜎}{\sqrt{𝑛}}\right)},\)\( temos \)𝐸[𝑍] = 0\( e \)𝑉(𝑍) = 1$.
\(\#\)Demonstração:
Sabemos que:
\(E\left(\bar{X}\right) = \mu\)
\(V\left(\bar{X}\right) = \sigma^2/n\)
Então, vamos avaliar a esperança e o desvio padrão de \(Z\):
\(E\left(Z\right) = E\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}}\right) = \frac{1}{\sigma/\sqrt{n}}E\left(\bar{X} - \mu\right) = \frac{1}{\sigma/\sqrt{n}}\left(E\left(\bar{X}\right) - E\left(\mu\right)\right) = \frac{1}{\sigma/\sqrt{n}} (\mu - \mu) = 0.\)
\(V\left(Z\right) = V\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}}\right) = \frac{1}{\left(\sigma/\sqrt{n}\right)^2}V\left(\bar{X}\right) = \frac{\sigma^2/n}{\sigma^2/n} = 1.\)
\(\#\)Q.E.D.
Exercício 6:#
Mostre que se \((𝑋_1,𝑋_2, … , 𝑋_𝑛)\) (todas independentes) é uma variável aleatória de uma população com média 𝜇 e variância \(\sigma^2\), então \(𝐸[\bar{X}] = 𝜇\) e \(𝑉(\bar{X}) = \sigma^2/n\).
\(\#\)Demonstração:
Sabemos que:
\(\bar{X} := \sum_{i=1}^n \frac{X_i}{n}\)
\(E(X_i) = \mu\)
\(V(X_i) = \sigma^2\)
Então,
\(E(\bar{X}) = E\left(\sum_{i=1}^n \frac{X_i}{n}\right) = E\left(\frac{1}{n}\sum_{i=1}^n X_i\right) = \frac{1}{n} \sum_{i=1}^n E(X_i) = \frac{1}{n} \sum_{i=1}^n \mu = \frac{1}{n} \cdot n\mu = \mu.\)
\(V(\bar{X}) = V\left(\sum_{i=1}^n \frac{X_i}{n}\right) = V\left(\frac{1}{n}\sum_{i=1}^n X_i\right) = \frac{1}{n^2} \sum_{i=1}^n V(X_i) =\\= \frac{1}{n^2} \sum_{i=1}^n \sigma^2 = \frac{1}{n^2} \cdot n\sigma^2 = \frac{\sigma^2}{n}.\)
Observação: \(\sum_{i=1}^n \alpha = \alpha + \alpha + \cdots +\alpha = n\cdot \alpha, \forall \alpha \in \mathbb{R}\).
Exercício extra (exercício de fixação 2 do material):#
O peso médio de \(n\) estudantes do sexo masculino de uma determinada universidade é 75,5 Kg e o desvio padrão é 7,5 Kg. Admitindo que os pesos são normalmente distribuídos, determine a quantidade (percentagem) de estudantes que pesam entre 60 e 80 Kg. Considere \(n=100, 200\) e \(500\). Compare os resultados.
\(X\): Peso de estudantes do sexo masculino
\(X \sim \mathcal{N}(75.5, 7.5)\)
\(P(60 < X < 80) = ?\)
\( P\left(\frac{60 - 75.5}{7.5} < Z < \frac{80 - 75.5}{7.5}\right) = P\left(-2.06 < Z < 0.6\right) = \\=P\left(Z < 0.6\right) - P\left(Z < -2.06\right) \approx 0.7257 - 0.0196 = 0.7060 = 70.60\%\)
Temos 70 alunos de 100 que pesam entre 60 e 80 kg.
Temos 141 alunos de 200 que pesam entre 60 e 80 kg.
Temos 353 alunos de 500 que pesam entre 60 e 80 kg.
(60-75.5)/7.5, (80-75.5)/7.5
(-2.066666666666667, 0.6)
st.norm.cdf(0.6), st.norm.cdf(-2.06), st.norm.cdf(0.6) - st.norm.cdf(-2.06)
(0.7257468822499265, 0.019699270409376895, 0.7060476118405495)
[(st.norm.cdf(0.6) - st.norm.cdf(-2.06))*n for n in [100, 200, 500]]
[70.60476118405495, 141.2095223681099, 353.02380592027475]
Aula 5 - Teste de hipóteses#
Francisco Aparecido Rodrigues, francisco@icmc.usp.br.
Universidade de São Paulo, São Carlos, Brasil.
https://sites.icmc.usp.br/francisco
Copyright: Creative Commons
Vamos resolver alguns exemplos usando simulação de Monte Carlo. Esses exemplos foram discutidos na aula.
Exemplo 1: Usando simulações#
Uma fábrica anuncia que o índice de nicotina dos cigarros de uma dada marca é igual a 20 mg por cigarro. Um laboratório realiza 20 análises do índice obtendo: 22, 19, 21, 22, 20, 18, 27, 20, 21, 19, 20, 22, 17, 20, 21,18, 25, 16, 20, 21. Sabe-se que o índice de nicotina dos cigarros dessa marca se distribui normalmente com variância 4 mg\(^2\). Pode-se aceitar a afirmação do fabricante, ao nível de 5%?
\(H_0: \mu = 20\)
\(H_1: \mu > 20\)
import numpy as np
import matplotlib.pyplot as plt
mu = 20 # hipotese a ser testada
sigma = 2 # desvio padrao populacional
n = 20 #tamanho da amostra
Ns = 1000 # numero de simulacoes
Xm=[] #distribuicao da media amostral
for s in range(1,Ns):
x = np.random.normal(mu, sigma, n) # sorteia uma amostra de tamanho n
Xm.append(np.mean(x))
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85,
label = str(Ns), density=True)
plt.axvline(x=mu, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)
Depois de gerar as amostras, vamos verificar a fração de observações que permitem que aceitemos \(H_0\).
X = [22, 19, 21, 22, 20, 18, 27, 20, 21, 19, 20, 22, 17, 20, 21,18, 25, 16, 20, 21]
xobs = np.mean(X)
alpha = 95
xc = np.percentile(Xm, alpha)
print('Xc=',xc,' Xobs = ', xobs)
if(xobs < xc):
print("Aceitamos H0")
else:
print("Rejeitamos H0")
Xc= 20.682130613144107 Xobs = 20.45
Aceitamos H0
Podemos ainda ver esse resultado na figura.
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
plt.axvline(x=xc, color='red', linestyle='--', label = 'xc1')
plt.axvline(x=xobs, color='green', linestyle='--', label = 'xobs')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.legend()
plt.show(True)
Exemplo 2: Usando simulações#
Um pesquisador deseja estudar o efeito de certa substância no tempo de reação de seres vivos a um certo tipo de estímulo. Um experimento é desenvolvido com cobaias, que são inoculadas com a substância e submetidas a um estímulo elétrico, com seus tempos de reação (em segundos) anotados. Os seguintes valores foram obtidos:
T = [9,1;9,3;7,2;13,3;10,9;7,2;9,9;8,0;8,6;7,5]
Admite-se que, em geral, o tempo de reação tem distribuição Normal com média 8 segundos e desvio padrão 2 segundos. Entretanto, o pesquisador desconfia que o tempo médio sofre alteração por influência da substância. Verifique a nível 6% se o tempo de reação das cobaias submetidas à substância foi alterado.
\(H_0: \mu = 8\)
\(H_1: \mu \neq 8\)
import numpy as np
import matplotlib.pyplot as plt
mu = 8
sigma = 2
n = 10
Ns = 10000
Xm=[] #distribuicao da media amostral
for s in range(1,Ns):
x = np.random.normal(mu, sigma, n) # sorteia uma amostra de tamanho n
Xm.append(np.mean(x))
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85,
label = str(Ns), density=True)
plt.axvline(x=mu, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)
X = [9.1,9.3,7.2,13.3,10.9,7.2,9.9,8.0,8.6,7.5]
xobs = np.mean(X)
alpha = 3 # ao nível 6%
xc1 = np.percentile(Xm, alpha)
xc2 = np.percentile(Xm, 100-alpha)
print('Xc1=',xc1, ' Xc2=', xc2, ' Xobs = ', xobs)
if(xobs < xc1 or xobs > xc2):
print("Rejeitamos H0")
else:
print("Aceitamos H0")
Xc1= 6.839844741968931 Xc2= 9.190933108643828 Xobs = 9.1
Aceitamos H0
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
plt.axvline(x=xc1, color='red', linestyle='--', label = 'xc1')
plt.axvline(x=xc2, color='orange', linestyle='--', label = 'xc2')
plt.axvline(x=xobs, color='green', linestyle='--', label = 'xobs')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.legend()
plt.show(True)
Exemplo 3: Solução exata#
Em uma cidade, acredita-se que 60% das pessoas aprovam o atual prefeito. Uma pesquisa foi realizada com 200 indivíduos, onde verificou-se que 104 disseram que aprovam o atual governo. Faça um teste ao nível 5% para determinar se a aprovação do prefeito é realmente igual a 60%.
import scipy.stats
alpha = 0.05
z = scipy.stats.norm.ppf(alpha)
print('P(Z < %1.2f) = %1.2f' % (z, alpha))
P(Z < -1.64) = 0.05
O valor crítico:
import numpy as np
p = 0.6
mu = p
s = p*(1-p)
n = 200
pc = z*(np.sqrt(p*(1-p)/200)) + p
print('pc = ', pc)
pc = 0.5430205989421221
pobs = 104/200
print('pobs = ',pobs)
pobs = 0.52
Como \(\hat{p}_{\mathrm{obs}} < \hat{p}_c \), rejeitamos \(H_0\) ao nível de 5%. Ou seja, a fração de pessoas que aprovam o atual prefeito é menor do que 60%.
Exemplo 4: Solução exata#
O número de horas de sono para uma pessoal saudável é próximo de 8 horas. por noite Pesquisadores suspeitam que um novo medicamento para depressão pode afetar esse número quando pacientes fazem seu uso. Para verificar essa influência do medicamento no número de horas de sono, são coletados dados de 10 pacientes, obtendo-se os valores 8,7,7,8,7,8,9,7,7,8, em horas. Verifique a hipótese dos pesquisadores ao nível 5%.
\(H_0: \mu = 8\)
\(H_1: \mu \neq 8\)
import scipy.stats
alpha = 0.05
n = 10
talpha = scipy.stats.t.ppf(alpha/2, n-1)
print('talpha = ',talpha)
talpha = -2.262157162740992
X = [8,7,7,8,7,8,9,7,7,8]
s = np.std(X, ddof=1)
xobs = np.mean(X)
print('s = ', s)
print('xobs = ',xobs)
s = 0.6992058987801011
xobs = 7.6
n = len(X)
m = 8
xc1 = m + talpha*s/np.sqrt(n)
xc2 = m - talpha*s/np.sqrt(n)
print('xc1 =',xc1)
print('xc2 =',xc2)
xc1 = 7.49981823162488
xc2 = 8.500181768375121
Como xobs está dentro do intervalo \([x_{c1},x_{c2}]\), aceitamos \(H_0\) ao nível 5%.
Valor p#
Vamos considerar um exemplo. Sejam as hipóteses:
\(H_0: \mu = 10\)
\(H_1: \mu < 10\)
Assumimos que a população tem distribuição uniforme com desvio padrão \(\sigma\), definido abaixo.
import numpy as np
import matplotlib.pyplot as plt
mu = 10
sigma = 2
n = 50
Ns = 10000
Xm=[] #distribuicao da media amostral
for s in range(1,Ns):
x = np.random.normal(mu, sigma, n) # sorteia uma amostra de tamanho n
Xm.append(np.mean(x))
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85,
label = str(Ns), density=True)
plt.axvline(x=mu, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)
Vamos supor que o valor observado \(\bar{x}_{obs}\) é definido abaixo. De acordo com o nível de significância, podemos aceitar ou rejeitar \(H_0\), conforme vemos abaixo.
xobs = 9.8
alphas = [5,10,20,25,30,40]
for alpha in alphas:
xc = np.percentile(Xm, alpha)
plt.figure(figsize=(6,3))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
plt.axvline(x=xc, color='red', linestyle='--', label = 'xc1')
plt.axvline(x=xobs, color='green', linestyle='--', label = 'xobs')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.legend()
if(xobs < xc):
plt.title("Rejeitamos H0"+r' $\alpha$ = ' + str(alpha/100))
else:
plt.title("Aceitamos H0"+r' $\alpha$ = ' + str(alpha/100))
plt.show(True)
O valor de \(\alpha\) em que há a transição entre aceitar ou rejeitar \(H_0\) é o valor p.
import numpy as np
xobs = 9.8
xcs = []
alphas = []
for alpha in np.arange(1,100,2):
xc = np.percentile(Xm, alpha)
xcs.append(xc)
alphas.append(alpha)
plt.figure(figsize=(8,4))
plt.plot(alphas,xcs)
plt.axhline(y=xobs, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\alpha$', fontsize=20)
plt.ylabel(r'$\bar{X}_c$', fontsize=20)
plt.grid(True)
plt.show(True)
Podemos calcular o valor p: \(P(\bar{X} > \bar{x}_{obs}|\mu=\mu_0) = \alpha\):
pvalue = 0
for i in range(0, len(Xm)):
if(Xm[i] < xobs):
pvalue = pvalue + 1
pvalue = pvalue/len(Xm)
print('P-valor: ', pvalue)
P-valor: 0.24122412241224123
Mostrando no gráfico anterior.
xobs = 9.8
xcs = []
alphas = []
for alpha in np.arange(1,100,2):
xc = np.percentile(Xm, alpha)
xcs.append(xc)
alphas.append(alpha)
plt.figure(figsize=(8,4))
plt.plot(alphas,xcs)
plt.axhline(y=xobs, color='red', linestyle='--', label = 'xobs')
plt.axvline(x=pvalue*100, color='blue', linestyle='--', label = 'p-valor')
plt.xlabel(r'$\alpha$', fontsize=20)
plt.ylabel(r'$\bar{X}$', fontsize=20)
plt.legend()
plt.grid(True)
plt.show(True)
Exemplo: Estudantes acreditam que a média da turma em um curso de estatística é igual a 65. O professor acredita que a média é maior. Para verificar essas hipóteses, ele seleciona notas de 10 estudantes, obtemos os valores [65, 65, 70, 67, 66, 63, 63, 68, 72, 71]. Assuma que as notas são normalmente distribuídas, calcule o valor p.
import numpy as np
X = [65, 65, 70, 67, 66, 63, 63, 68, 72, 71]
m = 65
n = len(X)
s = np.std(X, ddof=1)
xobs = np.mean(X)
print('s = ', s)
print('xobs = ',xobs)
s = 3.197221015541813
xobs = 67.0
talpha = (xobs - m)/(s/np.sqrt(n))
print('talpha = ', talpha)
talpha = 1.978141420187361
import scipy.stats
alpha = scipy.stats.t.cdf(talpha, n-1)
print('alpha =',1 - alpha)
alpha = 0.03964824393588806
Logo, o valor p é igual 0,039, indicando uma forte evidência para rejeitarmos \(H_0\).
Comparação de duas médias#
Uma das principais aplicações do teste de hipóteses é na seleção de atributos. Vamos comparar duas distribuições e verificar se elas possuem a mesma média. Vamos formular as hipóteses: $\( H_0: \mu_1 = \mu_2 \)\( \)\( H_a: \mu_1 \neq \mu_2 \)$
import numpy as np
import matplotlib.pyplot as plt
n = 1000
mu1 = 0
sigma1 = 2
x1 = np.random.normal(mu1, sigma1, n) # sorteia uma amostra de tamanho n
mu2 = 0
sigma2 = 2
x2 = np.random.normal(mu2, sigma2, n) # sorteia uma amostra de tamanho n
plt.figure(figsize=(8,4))
a1 = plt.hist(x=x1, bins=20, color='blue', alpha=0.7, rwidth=0.85, density=True)
a2 = plt.hist(x=x2, bins=20, color='red', alpha=0.7, rwidth=0.85, density=True)
plt.show(True)
Fazendo um teste de hipóteses para comparar as médias das duas distribuições:
from scipy import stats
t_stat, p = stats.ttest_ind(x1,x2)
print(f't={t_stat}, p={p}')
t=1.2607979946933925, p=0.20752884296966367
Ou seja, como o valor p é alto, podemos concluir que as distribuições possuem a mesma média (aceitamos \(H_0\))
Vamos agora considerar duas distribuições com médias distintas.
import numpy as np
import matplotlib.pyplot as plt
n = 1000
mu1 = 0
sigma1 = 2
x1 = np.random.normal(mu1, sigma1, n) # sorteia uma amostra de tamanho n
mu2 = 0.5
sigma2 = 2
x2 = np.random.normal(mu2, sigma2, n) # sorteia uma amostra de tamanho n
plt.figure(figsize=(8,4))
a1 = plt.hist(x=x1, bins=20, color='blue', alpha=0.7, rwidth=0.85, density=True)
a2 = plt.hist(x=x2, bins=20, color='red', alpha=0.7, rwidth=0.85, density=True)
plt.show(True)
from scipy import stats
t_stat, p = stats.ttest_ind(x1,x2)
print(f't={t_stat}, p={p}')
t=-4.871868681827615, p=1.19246724185128e-06
Nesse segundo caso, vemos que o valor p é próximo de zero, o que nos permite rejeitar \(H_0\)
Portanto, podemos usar o teste de hipóteses para realizar uma seleção de atributos, onde atributos que não conseguem discriminar duas classes, devem ser removidos do conjunto de dados.
Seleção de atributos#
Vamos incialmente gerar um conjunto de dados, onde as duas primeiras colunas possuem a mesma média para duas classes, mas as outras duas permite separar as classes.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
# duas primeiras variaveis
centers = [(0, 0), (0, 0)]
data = make_blobs(n_samples=100, centers=centers, cluster_std=3,
shuffle=False, random_state=42)
X1 = data[0] # atributos das observacoes
y = data[1] # classe conhecida inicialmente
# mostra os dados
plt.scatter(X1[:,0], X1[:,1], c=y, cmap='viridis', s=50, alpha=0.9)
plt.xlabel('x', fontsize=20)
plt.ylabel('y', fontsize=20)
plt.title('Conjunto de dados 1')
plt.show(True)
# terceira e quarta variaveis
centers = [(-3, -3), (1,1)]
data = make_blobs(n_samples=100, centers=centers, cluster_std=3,
shuffle=False, random_state=42)
X2 = data[0] # atributos das observacoes
y = data[1] # classe conhecida inicialmente
# mostra os dados
plt.scatter(X2[:,0], X2[:,1], c=y, cmap='viridis', s=50, alpha=0.9)
plt.xlabel('x', fontsize=20)
plt.ylabel('y', fontsize=20)
plt.title('Conjunto de dados 2')
plt.show(True)
Note quem as duas últimas variáveis permitem separar os dados tanto nos eixos x quando y.
Vamos construir um conjunto único com os dados gerados.
X = np.column_stack((X1,X2))
Vamos fazer a classificação usando todo o conjunto de dados.
from sklearn.model_selection import train_test_split
p = 0.2 # fraction of elements in the test set
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = p, random_state = 42)
from sklearn.naive_bayes import GaussianNB
from sklearn import metrics
model = GaussianNB()
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print('Accuracy: ', model.score(x_test, y_test))
Accuracy: 0.9
Para realizar a seleção dos atributos, usammos Anova (https://pt.wikipedia.org/wiki/Análise_de_variância), que usa o teste de hipóteses.
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif
fvalue_selector = SelectKBest(f_classif, k=2)
#seleciona os dois atributos mais importantes usando teste de hipoteses
X_kbest = fvalue_selector.fit_transform(X, y)
plt.scatter(X_kbest[:,0], X_kbest[:,1], c=y, cmap='viridis', s=50, alpha=0.9)
plt.show(True)
Notamos que quando selecionamos os dois principais atributos, o métod escolhe os atributos 3 e 4, conforme o esperado (veja o gráfico anterior).
Realizando a classificação usando apenas os atributos selecionados:
from sklearn.model_selection import train_test_split
p = 0.2 # fraction of elements in the test set
x_train, x_test, y_train, y_test = train_test_split(X_kbest, y, test_size = p,
random_state = 42)
from sklearn.naive_bayes import GaussianNB
from sklearn import metrics
model = GaussianNB()
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print('Accuracy: ', model.score(x_test, y_test))
Accuracy: 0.9
Logo, vemos que mesmo usando dois atributos, mantemos a taxa de acertos. Portanto, a seleção de atributos permitiu um modelo mais simples (com menos atributos), mas com o mesmo desempenho.
Aula 6 - Inferência Bayesiana#
Programa#
O paradigma Bayesiano.
Os diferentes tipos de prioris.
Distribuições conjugadas.
Estimação Bayesiana.
Densidade preditiva.
Computação Bayesiana.
Exemplos com PyMC3.
Referências e leituras recomendadas:
Migon, H. S., Gamerman, D. and Louzada, F. (2014). Statistical Inference: An Integrated Approach, Second Edition, CRC Press.
Caffo, B. (2016). Statistical Inference for Data Science. Leanpub. Disponível em https://leanpub.com/LittleInferenceBook
O paradigma Bayesiano#
Seja uma amostra aleatória \(X_1,\ldots, X_n\) que vem de um modelo \(p(x|\theta)\), \(\theta \in \Theta\) e sejam \(x_1,\ldots,x_n\) os dados observados.
Sob o paradigma clássico ou frequentista, \(\theta\) é um parâmetro fixo e desconhecido.
Sob o paradigma Bayesiano, consideramos modelos probabilísticos para representar a incerteza a respeito de \(\theta\).
Se temos informação a priori sobre o parâmetro \(\theta\), antes de observar os dados, por que não usá-la?
Exemplo:#
Na aplicação de dados bancários que vimos na Aula 1, suponha que antes de observar os dados, exista um conhecimento prévio de que a proporção p de inadimplentes esteja em torno de 20%. Como incorporar esse conhecimento prévio? Uma possibilidade seria considerar uma distribuição a priori beta para a proporção p.
# Mais sobre a distribuição beta: https://pt.wikipedia.org/wiki/Distribui%C3%A7%C3%A3o_beta
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from scipy.stats import beta
# Conjuntos de parâmetros da distribuição a priori para a proporção de inadimplentes
a_b_params = ((0.1, 0.1), (1, 1), (2, 8), (10, 5))
p = np.linspace(0, 1, 100)
# Plota as densidades da beta para cada conjunto de parâmetros
plt.figure(figsize=(13,3))
for i, (a, b) in enumerate(a_b_params):
plt.subplot(1, len(a_b_params), i+1)
prior = beta(a, b)
plt.plot(p, prior.pdf(p))
plt.xlabel(r'$p$')
plt.title("a = {:.1f}, b = {:.1f}".format(a, b))
plt.tight_layout()
Teorema de Bayes#
Ver, por exemplo: https://pt.wikipedia.org/wiki/Teorema_de_Bayes
Sejam
\(p(\theta)\) a distribuição a priori para \(\theta\).
\(l(\theta,x) = p(x|\theta)\) a verossimilhança de \(\theta\).
\(p(\theta|x)\) a distribuição a posteriori de \(\theta\).
O Teorema de Bayes ilustra o aumento de informação com a introdução do conhecimento a priori
Em outras palavras:
distribuição a posteriori \(\propto\) verossimilhança \(\times\) distribuição a priori
Para um valor fixo de \(x\), as duas fontes de informação para \(\theta\) são
a função \(l(\theta;x)=p(x|\theta)\) fornece a plausibilidade ou verossimilhança de cada um dos possíveis valores de \(\theta\) e
\(p(\theta)\) é chamada distribuição a priori de \(\theta\),
que, combinadas, levam à distribuição a posteriori de \(\theta\), \(p(\theta|x)\).
Assim, a forma usual do teorema de Bayes é \(p(\theta|x) \propto l(\theta;x)p(\theta).\)
O termo omitido \(p(x)\) é apenas uma constante normalizadora e não depende de \(\theta\).
Para \(x\) fixo, a verossimilhança fornece a plausibilidade de cada um dos possíveis valores de \(\theta\).
Já a distribuição a priori \(p(\theta)\) incorpora o conhecimento do pesquisador.
Essas duas quantidades combinadas são levadas à distribuição a posteriori de \(\theta\).
A distribuição a posteriori de \(\theta\) dados \(x_1,\ldots,x_n\) observados é dada por:
\(p(x_1,\ldots,x_n|\theta) = \displaystyle\prod_{i=1}^n p(x_i|\theta) = L(\theta|x_1,\ldots,x_n)\) é a função de verossimilhança de \(\theta\).
O denominador $\(\int_{\Theta} p(x_1,\ldots,x_n|\theta)p(\theta)d\theta = C(x_1,\ldots,x_n).\)$
É comum escrever que
\(p(\theta|x_1,\ldots,x_n) = \displaystyle\frac{ L(\theta|x_1,\ldots,x_n) p(\theta)}{ C(x_1,\ldots,x_n)}\propto L(\theta|x_1,\ldots,x_n) p(\theta)\),
Distribuição preditiva#
A constante normalizadora da posteriori pode ser facilmente recuperada pois \(p(\theta|x)=kp(x|\theta)p(\theta)\) onde
\(k^{-1}= \int p(x|\theta)p(\theta)d\theta=E_\theta[p(X|\theta)]= p(x)\)
chamada distribuição preditiva (ou marginal) de \(X\).
Esta é a distribuição esperada para a observação \(x\) dado \(\theta\). Assim,
Antes de observar \(X\) podemos checar a adequação da priori fazendo predições via \(p(x)\).
Se \(X\) observado recebia pouca probabilidade preditiva então o modelo deve ser questionado, revisado, ou existe observação aberrante.
Se, após observar \(X=x\), estamos interessados na previsão de uma quantidade \(Y\), também relacionada com \(\theta\), e descrita probabilisticamente por \(p(y|\theta)\) então
Os conceitos de priori e posteriori são relativos àquela observação que está sendo considerada no momento. Assim, \(p(\theta| x)\) é a posteriori de \(\theta\) em relação a \(X\) (que já foi observado) mas é a priori de \(\theta\) em relação a \(Y\) (que não foi observado ainda).
Após observar \(Y=y\) uma nova posteriori (relativa a \(X=x\) e \(Y=y\)) é obtida aplicando-se novamente o teorema de Bayes.
Exemplo (Gamerman e Migon, 1993)
Um médico “desconfia” que um paciente pode ter uma doença. Baseado na sua experiência, no seu conhecimento sobre esta doença e nas informações dadas pelo paciente, ele assume que a probabilidade do paciente ter a doença é 0.7. A quantidade de interesse, desconhecida, é definida como
\(\theta = \left\{\begin{array}{l} 1,\quad \mbox{se o paciente tem a doença,} \\ 0,\quad \mbox{se o paciente não tem a doença.}\end{array}\right.\)
Para aumentar sua quantidade de informação sobre a doença o médico aplica um teste \(X\) relacionado com \(\theta\) através da distribuição
\(P(X=1|\theta=0)=0.40\) e
\(P(X=1|\theta=1)=0.95\)
e o resultado do teste foi positivo (ou seja, observou-se \(X=1\)).
É bem intuitivo que a probabilidade de doença deve ter aumentado após este resultado e a questão aqui é quantificar este aumento. Usando o teorema de Bayes, segue que
\(P(\theta=1|X=1)\propto l(\theta=1;X=1)p(\theta=1)=(0.95)(0.7)=0.665\)
\(P(\theta=0|X=1)\propto l(\theta=0;X=1)p(\theta=0)=(0.40)(0.3)=0.120.\)
A constante normalizadora é tal que \(P(\theta=0|X=1)+P(\theta=1|X=1)=1\), i.e.,
\(k(0.665)+k(0.120)=1\) e \(k=1/0.785\).
Portanto, a distribuição a posteriori de \(\theta\) é
\(P(\theta=1|X=1)=0.665/0.785=0.847\)
\(P(\theta=0|X=1)=0.120/0.785=0.153.\)
O aumento na probabilidade de doença não foi muito grande porque a verossimilhança \(l(\theta=0;X=1)\) também era grande (o modelo atribuia uma plausibilidade grande para \(\theta=0\) mesmo quando \(X=1\)).
Agora o médico aplica outro teste \(Y\) cujo resultado está relacionado a \(\theta\) através da seguinte distribuição
\(P(Y=1|\theta=0)=0.04\) e
\(P(Y=1|\theta=1)=0.99.\)
Mas antes de observar o resultado deste teste é interessante obter sua distribuição preditiva.
Como \(\theta\) é uma quantidade discreta segue que
\(p(y|x)=\sum_\theta p(y|\theta)p(\theta|x)\)
e note que \(p(\theta|x)\) é a priori em relação a \(Y\).
Assim,
\(P(Y=1|X=1)=P(Y=1|\theta=0)P(\theta=0|X=1)+P(Y=1|\theta=1)P(\theta=1|X=1)\)
\(=(0.04)(0.153) + (0.99)(0.847) = 0.845\)
\(P(Y=0|X=1)=1-P(Y=1|X=1) = 0.155.\)
O resultado deste teste foi negativo (\(Y=0\)).
Neste caso, é também intuitivo que a probabilidade de doença deve ter diminuido e esta redução será quantificada por uma nova aplicação do teorema de Bayes,
\(P(\theta=1|X=1,Y=0)\propto\displaystyle l(\theta=1;Y=0)P(\theta=1|X=1)\propto\displaystyle (0.01)(0.847)=0.0085\)
\(P(\theta=0|X=1,Y=0)\propto\displaystyle l(\theta=0;Y=0)P(\theta=0|X=1)\propto\displaystyle (0.96)(0.153)=0.1469.\)
A constante normalizadora é \(1/(0.0085+0.1469)=1/0.1554\) e assim a distribuição a posteriori de \(\theta\) é
\(P(\theta=1|X=1,Y=0)=0.0085/0.1554=0.055\)
\(P(\theta=0|X=1,Y=0)=0.1469/0.1554=0.945.\)
Verifique como a probabilidade de doença se alterou ao longo do experimento
\(P(\theta=1) = \left\{\begin{array}{ll} 0.7 & \mbox{antes dos testes}\\0.847 & \mbox{após o teste X}\\ 0.055 & \mbox{após X e Y}\end{array}\right.\)
Note também que o valor observado de \(Y\) recebia pouca probabilidade preditiva. Isto pode levar o médico a repensar o modelo, i.e.,
(i) Será que \(P(\theta =1)=0.7\) é uma priori adequada?
(ii) Será que as distribuições amostrais de \(X\) e \(Y\) estão corretas? O teste \(X\) é tão inexpressivo e \(Y\) é realmente tão poderoso?
Os diferentes tipos de prioris#
Destacamos as distribuições a priori
Priori não-informativa
Uniforme
Priori vaga (às vezes imprópria)
Priori informativa
Conhecimento do pesquisador dá informação sobre os parâmetros
Priori conjugada
Priori e posteriori tem a mesma distribuição, a menos dos parâmetros (em geral facilita os cálculos)
Distribuições conjugadas#
Leituras recomendadas:
Notas Ricardo Ehlers e Paulo Justiniano: http://www.leg.ufpr.br/~paulojus/CE227/ce227/node1.html
Conjugate Prior Explained, with examples & proofs: https://towardsdatascience.com/conjugate-prior-explained-75957dc80bfb
Vantagem de usar distribuições a priori conjugadas: principalmente ganho de custo computacional.
| Priori | Núcleo da Verossimilhança | Posteriori | |
|---|---|---|---|
| $\theta$ proporção | Beta | Bernoulli | Beta |
| $\theta$ média | Normal | Normal | Normal |
| taxa de falha | Gama | Poisson | Gama |
| Dirichlet | Multinomial | Dirichlet |
Exemplo de priori conjugada beta-Bernoulli#
Ver https://towardsdatascience.com/conjugate-prior-explained-75957dc80bfb
No exemplo do banco, se considerarmos que
\(X=\left\{ \begin{array}{lll} 1, &\mbox{se o cliente é classificado como inadimplente,} \\ 0, &\mbox{caso contrário.} \end{array}\right.\)
\(X \sim Bernoulli(p)\)
Verossimilhança:
Para \(n\) suficientemente grande, pelo TLC sabemos que a distribuição amostral de \(\bar{X}\) se aproxima da normal (será usada para graficar a verossimilhança) $\(\bar{X} \sim N\left(p, \displaystyle{\frac{p(1-p)}{n}}\right).\)$
Além disso, \(Y = \sum_{i=1}^{n} X_i \sim binomial(n, p)\).
Priori: \(p \sim beta(2, 8)\)
Posteriori: \(p|k \sim beta(k+a, n-k+b)\)
onde \(k\) é o número de sucessos observados na amostra.
import pandas as pd
# Indique o seu diretório se necessário
#pkgdir = '/hdd/MBA/ECD/Data'
#dados = pd.read_csv(f'{pkgdir}/dados_banco.csv', index_col=0)
# Dados banco - Leitura dos dados
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
dados.head()
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|---|---|
| Cliente | |||||||||
| 75928 | M | 32 | Privada | 5719.00 | 933.79 | 0.0 | 0.0 | 6023.68 | 0 |
| 52921 | F | 28 | Privada | 5064.00 | 628.37 | 0.0 | 0.0 | 1578.24 | 0 |
| 8387 | F | 24 | Autônomo | 4739.00 | 889.18 | 0.0 | 0.0 | 2578.70 | 0 |
| 54522 | M | 30 | Pública | 5215.00 | 1141.47 | 0.0 | 0.0 | 4348.96 | 0 |
| 45397 | M | 30 | Autônomo | 5215.56 | 520.70 | 0.0 | 0.0 | 1516.78 | 1 |
# Vamos trabalhar com uma amostra
import random
a = 2
b = 8
amostra = dados.sample(n=500, replace=False, random_state=10)
n = len(amostra)
k = amostra['Inadimplente'].sum()
posteriori = beta(a + k, n - k + b)
k/n
0.268
from scipy.stats import norm
# Eixo x entre 0 e 1 de .002 em .002.
x_axis = np.arange(0, 1, 0.002)
# Plota as densidades da beta para cada conjunto de parâmetros
plt.figure(figsize=(20,6))
prior = beta(a, b)
p_chapeu = amostra['Inadimplente'].mean()
dp = np.sqrt(p_chapeu*(1-p_chapeu)/n)
media = p_chapeu
dp = np.sqrt(media*(1-media)/n)
plt.s = 0
plt.rcParams.update({'font.size': 22})
plt.plot(x_axis, norm.pdf(x_axis, media, dp), label='verossimilhança')
plt.plot(x_axis, prior.pdf(x_axis), label='priori')
plt.plot(x_axis, posteriori.pdf(x_axis), label='posteriori')
plt.xlabel(r'$p$')
plt.legend()
<matplotlib.legend.Legend at 0x7fb310141900>
# Estimador bayesiano EAP (Esperança a posteriori)
print('Média: %.2f' % posteriori.mean())
# E para calcular um intervalo de credibilidade, decidimos uma probabilidade
# Por exemplo 95% para a credibilidade
# Uma maneira seria definir que 2,5% de cada cauda como os limites do intervalo (chamado intervalo simétrico)
# Este método é válido quando a posteriori se aproxima de uma distribuição simétrica, pois nesse caso tende a gerar o intervalo com menor amplitude
# A seguir, apresentamos outra solução com um intervalo de credibilidade de menor amplitude.
LI = posteriori.ppf(.025)
LS = posteriori.ppf(.975)
print("Intervalo com 95% de credibilidade: ({:.3f}, {:.3f})".format(LI,LS))
Média: 0.27
Intervalo com 95% de credibilidade: (0.229, 0.306)
Estimação Bayesiana#
Leituras recomendadas:
Notas Ricardo Ehlers e Paulo Justiniano: http://www.leg.ufpr.br/~paulojus/CE227/ce227/node1.html
Migon, H. S., Gamerman, D. and Louzada, F. (2014). Statistical Inference: An Integrated Approach, Second Edition, CRC Press.
Introdução à Teoria da Decisão#
Um problema de decisão fica completamente especificado pela descrição dos seguintes espaços:
(i) Espaço do parâmetro ou estados da natureza, \(\Theta\).
(ii) Espaço dos resultados possíveis de um experimento, \(\Omega\).
(iii) Espaço de possíveis ações, \(A\).
Uma regra de decisão \(\delta\) é uma função definida em \(\Omega\) que assume valores em \(A\), i.e. \(\delta:\Omega\rightarrow A\). A cada decisão \(\delta\) e a cada possível valor do parâmetro \(\theta\) podemos associar uma perda \(L(\delta,\theta)\) assumindo valores positivos. Definimos assim uma função de perda.
Definição: O risco de uma regra de decisão, denotado por \(R(\delta)\), é a perda esperada a posteriori, i.e. $\(R(\delta)=E_{\theta\vert\mathbf{x}} [L(\delta,\theta)].\)$
Uma regra de decisão \(\delta^*\) é ótima se tem risco mínimo, i.e. \(R(\delta^*)<R(\delta), ~\forall \delta\). Esta regra será denominada regra de Bayes e seu risco, risco de Bayes.
Exemplo:
Um laboratório farmaceutico deve decidir pelo lançamento ou não de uma nova droga no mercado. O laboratório só lançará a droga se achar que ela é eficiente mas isto é exatamente o que é desconhecido.
Podemos associar um parâmetro \(\theta\) aos estados da natureza:
droga é eficiente (\(\theta=1\)),
droga não é eficiente (\(\theta=0\))
e as possíveis ações como
lança a droga (\(\delta=1\)),
não lança a droga (\(\delta=0\)).
Suponha que foi possível construir a seguinte tabela de perdas levando em conta a eficiência da droga,
| eficiente ($\theta=1$) | não eficiente ($\theta=0$) | |
|---|---|---|
| lança ($\delta=1$) | -500 | 600 |
| não lança ($\delta=0$) | 1500 | 100 |
Vale notar que estas perdas traduzem uma avaliação subjetiva em relação à gravidade dos erros cometidos.
Suponha agora que a incerteza sobre os estados da natureza é descrita por
\(P(\theta=1)=\pi\), \(0<\pi<1\)
avaliada na distribuição atualizada de \(\theta\) (seja a priori ou a posteriori).
Note que, para \(\delta\) fixo, \(L(\delta,\theta)\) é uma variável aleatória discreta assumindo apenas dois valores com probabilidades \(\pi\) e \(1-\pi\).
Assim, usando a definição de risco obtemos que
\(\displaystyle R(\delta=0) = \displaystyle E(L(0,\theta))=\pi 1500 +(1-\pi) 100 = 1400\pi + 100\)
\(\displaystyle R(\delta=1) = \displaystyle E(L(1,\theta))=\pi(-500)+(1-\pi) 600 =-1100\pi + 600\)
Uma questão que se coloca aqui é, para que valores de \(\pi\) a regra de Bayes será de lançar a droga.
Não é difícil verificar que as duas ações levarão ao mesmo risco, i.e.
\(R(\delta=0)=R(\delta=1)\) se somente se \(\pi=0.20\).
Além disso, para \(\pi<0.20\) temos que \(R(\delta=0)<R(\delta=1)\) e a regra de Bayes consiste em não lançar a droga enquanto que \(\pi>0.20\) implica em \(R(\delta=0)>R(\delta=1)\) e a regra de Bayes deve ser de lançar a droga.
Estimadores de Bayes#
Considere uma amostra aleatória \(X_1,\dots,X_n\), tomada de uma distribuição com função de (densidade) de probabilidade \(p(x\vert\theta)\), onde o valor do parâmetro \(\theta\) é desconhecido.
Em um problema de inferência como este o valor de \(\theta\) deve ser estimado a partir dos valores observados na amostra.
Se \(\theta\in\Theta\) então é razoável que os possíveis valores de um estimador \(\widehat{\theta}(\mathbf{x})\) também devam pertencer ao espaço \(\Theta\).
Além disso, um bom estimador é aquele para o qual, com alta probabilidade, apresenta o seguinte erro $\(\widehat{\theta}(\mathbf{x})-\theta\)$ próximo de zero.
Funções de perda#
Para cada possível valor de \(\theta\) e cada possível estimativa \(a\in\Theta\) vamos associar uma perda \(L(a,\theta)\) de modo que quanto maior a distância entre \(a\) e \(\theta\) maior o valor da perda. A perda esperada a posteriori é dada por
O estimador de Bayes será aquele que minimiza a perda esperada.
Função de perda quadrática#
O estimador de Bayes para \(\theta\) será a média de sua distribuição atualizada (EAP: expected a posteriori).
Função de perda absoluta#
(introduz punições que crescem linearmente com o erro de estimação)
O estimador de Bayes para \(\theta\) é a mediana de sua distribuição atualizada.
Função de perda 0-1#
(associam uma perda fixa a um erro cometido, não importando sua magnitude)
para todo \(\epsilon>0\).
Neste caso, o estimador de Bayes é a moda da distribuição atualizada de \(\theta\) (MAP: maximum a posteriori).
A moda da posteriori de \(\theta\) também é chamado de estimador de máxima verossimilhança generalizado (EMVG) e é o mais fácil de ser obtido dentre os estimadores vistos até agora.
Exemplo:
Suponha que queremos estimar a proporção \(\theta\) de itens defeituosos em um grande lote. Para isto será tomada uma amostra aleatória \(X_1,\dots,X_n\) de uma distribuição de Bernoulli com parâmetro \(\theta\). Usando uma priori conjugada beta(\(a,b\)), após observar a amostra a distribuição a posteriori é beta(\(a+k,b+n-k\)) onde \(k=\sum_{i=1}^n x_i\).
A média desta distribuição beta é dada por \((a+k)/(a+b+n)\) e portanto o estimador de Bayes de \(\theta\) usando perda quadrática é
\(\delta(\mathbf{x})= \displaystyle\frac{a+\sum_{i=1}^n X_i}{a+b+n}.\)
Estimação por Intervalos#
Pode-se desenvolver a estimação por intervalos por meio do intervalo de credibilidade (ou intervalo de confiança Bayesiano) baseado no distribuição a posteriori.
Definição:
C é um intervalo de credibilidade de 100(1-\(\alpha\))\(\%\), ou nível de credibilidade (ou confiança) \(1-\alpha\), para \(\theta\) se \(P(\theta\in C)\ge 1-\alpha\).
Obs: Quanto menor for o tamanho do intervalo, mais concentrada é a distribuição do parâmetro, ou seja o tamanho do intervalo informa sobre a dispersão de \(\theta\).
O intervalo com o menor comprimento possível é obtido tomando-se os valores de \(\theta\) com maior densidade a posteriori, e esta idéia é expressa matematicamente na definição abaixo.
Definição:
Um intervalo de credibilidade \(C\) de 100(1-\(\alpha\))% para \(\theta\) é de máxima densidade a posteriori (MDP) se \(C=\{\theta\in\Theta:p(\theta\vert\mathbf{x})\ge k(\alpha)\}\) onde \(k(\alpha)\) é a maior constante tal que \(P(\theta\in C)\ge 1-\alpha\).
Usando esta definição, todos os pontos dentro do intervalo MDP terão densidade maior do que qualquer ponto fora do intervalo.
Um problema com os intervalos MDP é que eles não são invariantes a transformações 1 a 1, a não ser para transformações lineares. O mesmo problema ocorre com intervalos de comprimento mínimo na inferência clássica.
Computação Bayesiana#
Leituras recomendadas:
Métodos de Monte Carlo: https://pt.wikipedia.org/wiki/Método_de_Monte_Carlo
Notas Ricardo Ehlers e Paulo Justiniano: http://www.leg.ufpr.br/~paulojus/CE227/ce227/node1.html
Salvatier, John; Wiecki, Thomas V.; Fonnesbeck, Christopher. Probabilistic programming in Python using PyMC3. PeerJ Computer Science, v. 2, p. e55, 2016. Disponível em https://peerj.com/articles/cs-55/. Acessado em 06/04/2021.
A obtenção de informações a partir da distribuição a posteriori dos parâmetros pode envolver a avaliação de probabilidades ou esperanças, que exigem métodos computacionais baseados em simulações, como
Método de Monte Carlo simples
Monte Carlo com função de importância,
Algoritmo de Metropolis-Hastings,
Amostrador de Gibbs,
Método do Bootstrap Bayesiano,
Monte Carlo via cadeias de Markov (MCMC),
Monte Carlo Hamiltoniano (HMC),
No-U-Turn Sampler (NUTS).
HMC e NUTS aproveitam as informações de gradiente da probabilidade e alcançam uma convergência muito mais rápida do que os métodos de amostragem tradicionais, especialmente para modelos mais complexos.
O pacote PyMC3 do Python usam a programação probabilística para executar os métodos de HMC como o NUTS. Esse tipo de programação permite a especificação flexível e ajuste de modelos estatísticos bayesianos com sintaxe intuitiva e legível, embora poderosa, que é próxima da sintaxe natural que os estatísticos usam para descrever modelos.
Exemplos#
!pip install theano
!pip install arviz==0.15.1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting theano
Downloading Theano-1.0.5.tar.gz (2.8 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.8/2.8 MB 44.4 MB/s eta 0:00:00
?25h Preparing metadata (setup.py) ... ?25l?25hdone
Requirement already satisfied: numpy>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from theano) (1.22.4)
Requirement already satisfied: scipy>=0.14 in /usr/local/lib/python3.10/dist-packages (from theano) (1.10.1)
Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from theano) (1.16.0)
Building wheels for collected packages: theano
Building wheel for theano (setup.py) ... ?25l?25hdone
Created wheel for theano: filename=Theano-1.0.5-py3-none-any.whl size=2668109 sha256=da55416411fe89a525787e2707fc012f1bc6d368d17de994c8779baa5f2948bb
Stored in directory: /root/.cache/pip/wheels/d9/e6/7d/2267d21a99e4ab8276f976f293b4ff23f50c9d809f4a216ebb
Successfully built theano
Installing collected packages: theano
Successfully installed theano-1.0.5
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: arviz==0.15.1 in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.22.4)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.10.1)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (23.1)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (2022.12.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.1.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (0.5.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz==0.15.1) (3.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (4.39.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.4.4)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz==0.15.1) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz==0.15.1) (1.16.0)
import warnings
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm
#!pip show arviz
Exemplo beta-Bernoulli: clientes do banco#
with pm.Model() as model:
p = pm.Beta("p", 2, 8) #priori
obs = pm.distributions.discrete.Bernoulli("obs", p, observed=amostra['Inadimplente'])
idata = pm.sample(2000, tune=1500, return_inferencedata=True)
az.summary(idata)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| p | 0.267 | 0.019 | 0.231 | 0.303 | 0.0 | 0.0 | 2111.0 | 2931.0 | 1.0 |
az.plot_posterior(idata);
az.plot_forest(idata, r_hat=True);
Exemplo: normal com dados simulados#
Fonte: https://docs.pymc.io/pymc-examples/examples/pymc3_howto/api_quickstart.html
with pm.Model() as model:
mu = pm.Normal("mu", mu=0, sigma=1) # priori
obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100)) # verossimilhança
model.basic_RVs
[mu, obs]
model.free_RVs
[mu]
model.observed_RVs
[obs]
Variáveis aleatórias não observáveis#
with pm.Model():
x = pm.Normal("x", mu=0, sigma=1)
Variáveis aleatórias observáveis#
with pm.Model():
obs = pm.Normal("x", mu=0, sigma=1, observed=np.random.randn(100))
Inferência#
Amostragem#
with pm.Model() as model:
mu = pm.Normal("mu", mu=0, sigma=1)
obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100))
idata = pm.sample(2000, tune=1500, return_inferencedata=True)
idata.posterior.dims
Frozen({'chain': 2, 'draw': 2000})
Amostragem com 6 cadeias em paralelo#
with pm.Model() as model:
mu = pm.Normal("mu", mu=0, sigma=1)
obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100))
idata = pm.sample(cores=4, chains=6, return_inferencedata=True)
idata.posterior["mu"].shape
(6, 1000)
Podemos incluir passos com outros métodos#
with pm.Model() as model:
mu = pm.Normal("mu", mu=0, sigma=1)
obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100))
step = pm.Metropolis()
trace = pm.sample(1000, step=step)
with pm.Model() as model:
mu = pm.Normal("mu", mu=0, sigma=1)
sd = pm.HalfNormal("sd", sigma=1)
obs = pm.Normal("obs", mu=mu, sigma=sd, observed=np.random.randn(100)) #verossimilhança
step1 = pm.Metropolis(vars=[mu])
step2 = pm.Slice(vars=[sd])
idata = pm.sample(10000, step=[step1, step2], cores=4, return_inferencedata=True)
Análise de resultados#
az.plot_trace(idata);
Estatística de Gelman-Rubin (R chapéu)#
az.summary(idata)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| mu | 0.012 | 0.108 | -0.188 | 0.219 | 0.001 | 0.001 | 6116.0 | 5771.0 | 1.0 |
| sd | 1.113 | 0.079 | 0.969 | 1.262 | 0.000 | 0.000 | 39031.0 | 29563.0 | 1.0 |
az.plot_forest(idata, r_hat=True);
az.plot_posterior(idata);
az.plot_posterior(idata);
Prática 1#
Inferência Bayesiana#
Exercício:#
Na aplicação de dados bancários que vimos na Aula 1 (clientes do banco), considere diferentes tamanhos de amostra e diferentes prioris e veja como interferem na posteriori.
Além disso, altere os parâmetros da distribuição a priori (chamados hiperparâmetros) e verifique os efeitos na distribuição a posteriori.
Considere, por exemplo, uma distribuição a priori beta para a proporção p, com diferentes hiperparâmetros. Compare os resultados com os obtidos com uma distribuição a priori uniforme para p.
Exemplo de priori conjugada beta-Bernoulli#
Ver https://towardsdatascience.com/conjugate-prior-explained-75957dc80bfb
No exemplo do banco, se considerarmos que
\(X=\left\{ \begin{array}{lll} 1, &\mbox{se o cliente é classificado como inadimplente,} \\ 0, &\mbox{caso contrário.} \end{array}\right.\)
\(X \sim Bernoulli(p)\)
Verossimilhança:
Para \(n\) suficientemente grande, pelo TLC sabemos que a distribuição amostral de \(\bar{X}\) se aproxima da normal $\(\bar{X} \sim N\left(p, \displaystyle{\frac{p(1-p)}{n}}\right)\)$
Além disso, \(Y = \sum_{i=1}^{n} X_i \sim binomial(np, np(1-p))\).
import pandas as pd
# Dados banco - Leitura dos dados
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
dados.head()
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|---|---|
| Cliente | |||||||||
| 75928 | M | 32 | Privada | 5719.00 | 933.79 | 0.0 | 0.0 | 6023.68 | 0 |
| 52921 | F | 28 | Privada | 5064.00 | 628.37 | 0.0 | 0.0 | 1578.24 | 0 |
| 8387 | F | 24 | Autônomo | 4739.00 | 889.18 | 0.0 | 0.0 | 2578.70 | 0 |
| 54522 | M | 30 | Pública | 5215.00 | 1141.47 | 0.0 | 0.0 | 4348.96 | 0 |
| 45397 | M | 30 | Autônomo | 5215.56 | 520.70 | 0.0 | 0.0 | 1516.78 | 1 |
Considere diferentes tamanhos de amostra. Como interferem na posteriori?
# Vamos trabalhar com uma amostra
from scipy.stats import beta
import numpy as np
import matplotlib.pyplot as plt
import random
a = 2
b = 8
amostra = dados.sample(n=500, replace=False, random_state=10)
n = len(amostra)
k = amostra['Inadimplente'].sum()
posteriori = beta(a + k, n - k + b)
k/n
0.268
Considere diferentes tipos de priori, por exemplo beta ou uniforme. Como interferem na posteriori?
Priori 1 - beta#
Priori: \(p \sim beta(2, 8)\)
Posteriori: \(p|k \sim beta(k+a, n-k+b)\)
onde \(k\) é o número de sucessos observados na amostra.
from scipy.stats import norm
# Eixo x entre 0 e 1 de .002 em .002.
x_axis = np.arange(0, 1, 0.002)
# Plota as densidades da beta para cada conjunto de parâmetros
plt.figure(figsize=(20,6))
prior = beta(a, b)
p_chapeu = amostra['Inadimplente'].mean()
dp = np.sqrt(p_chapeu*(1-p_chapeu)/n)
media = p_chapeu
dp = np.sqrt(media*(1-media)/n)
plt.s = 0
plt.rcParams.update({'font.size': 22})
plt.plot(x_axis, norm.pdf(x_axis, media, dp), label='verossimilhança')
plt.plot(x_axis, prior.pdf(x_axis), label='priori')
plt.plot(x_axis, posteriori.pdf(x_axis), label='posteriori')
plt.xlabel(r'$p$')
plt.legend()
<matplotlib.legend.Legend at 0x7fb8a1f9c310>
# Estimador bayesiano EAP (Esperança a posteriori)
print('Média: %.2f' % posteriori.mean())
# E para calcular um intervalo de credibilidade, decidimos uma probabilidade
# Por exemplo 95% para a credibilidade
# Uma maneira seria definir que 2,5% de cada cauda como os limites do intervalo (chamado intervalo simétrico)
# Este método é válido quando a posteriori se aproxima de uma distribuição simétrica, pois nesse caso tende a gerar o intervalo com menor amplitude
# A seguir, apresentamos outra solução com um intervalo de credibilidade de menor amplitude.
LI = posteriori.ppf(.025)
LS = posteriori.ppf(.975)
print("Intervalo com 95% de credibilidade: {:.3f}, {:.3f})".format(LI,LS))
Média: 0.27
Intervalo com 95% de credibilidade: 0.229, 0.306)
Exemplos#
#!pip install pymc3==3.11.1
!pip install theano
!pip install arviz==0.15.1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: theano in /usr/local/lib/python3.10/dist-packages (1.0.5)
Requirement already satisfied: numpy>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from theano) (1.22.4)
Requirement already satisfied: scipy>=0.14 in /usr/local/lib/python3.10/dist-packages (from theano) (1.10.1)
Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from theano) (1.16.0)
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: arviz==0.15.1 in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.22.4)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.10.1)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (23.1)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (2022.12.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.1.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (0.5.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz==0.15.1) (3.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (4.39.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.4.4)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz==0.15.1) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz==0.15.1) (1.16.0)
import warnings
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm
Modelo beta-Bernoulli: clientes do banco#
Com amostra de 500 observações, considere, por exemplo, priori beta (10,5). Compare com os resultados obtidos em aula.
with pm.Model() as model:
p = pm.Beta("p", 10, 5)
obs = pm.distributions.discrete.Bernoulli("obs", p, observed=amostra['Inadimplente'])
idata = pm.sample(2000, tune=1500, return_inferencedata=True)
az.summary(idata)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| p | 0.279 | 0.02 | 0.241 | 0.314 | 0.0 | 0.0 | 1759.0 | 2476.0 | 1.0 |
az.plot_posterior(idata);
az.plot_forest(idata, r_hat=True);
Análise de resultados#
az.plot_trace(idata);
az.summary(idata)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| p | 0.279 | 0.02 | 0.241 | 0.314 | 0.0 | 0.0 | 1759.0 | 2476.0 | 1.0 |
az.plot_forest(idata, r_hat=True);
az.plot_posterior(idata);
Priori uniforme#
Considere agora, por exemplo, priori uniforme para a proporção p. Como interfere na posteriori?
Refaça as análises com diferentes tamanhos de amostra, por exemplo 100 ou 10000.
with pm.Model() as model:
p = pm.Uniform("p")
obs = pm.distributions.discrete.Bernoulli("obs", p, observed=amostra['Inadimplente'])
idata = pm.sample(2000, tune=1500, return_inferencedata=True)
az.summary(idata)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| p | 0.269 | 0.02 | 0.234 | 0.307 | 0.001 | 0.0 | 1434.0 | 2742.0 | 1.0 |
az.plot_posterior(idata);
az.plot_forest(idata, r_hat=True);
### Análise de resultados
az.plot_trace(idata);
az.summary(idata)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| p | 0.269 | 0.02 | 0.234 | 0.307 | 0.001 | 0.0 | 1434.0 | 2742.0 | 1.0 |
az.plot_forest(idata, r_hat=True);
az.plot_posterior(idata);
Prática 2#
Inferência Bayesiana#
Aplicação#
Traduzida e adaptada de https://github.com/WillKoehrsen/probabilistic-programming/blob/master/Estimating Probabilities with Bayesian Inference.ipynb
Suponha que visitemos uma reserva de animais selvagens onde sabemos que os únicos animais são leões, tigres e ursos, mas não sabemos quantos de cada há nesse local. Durante o passeio, vemos 3 leões, 2 tigres e 1 urso. Supondo que todos os animais tenham chances iguais de aparecer em nossa amostra, estime a prevalência de cada espécie. Qual é a probabilidade de o próximo animal que vemos ser um urso?
Modelo
O modelo subjacente é multinomial com parâmetros \( p_k \) (veja, por exemplo, https://en.wikipedia.org/wiki/Multinomial_distribution).
A distribuição a priori de \( p_k \) é uma distribuição Dirichlet (veja, por exemplo, https://en.wikipedia.org/wiki/Dirichlet_distribution).
O vetor \( \alpha \) é um parâmetro da distribuição a priori de Dirichlet, também chamado de hiperparâmetro de concentração.
Uma Distribuição Multinomial com uma Priori de Dirichlet é referida como uma Distribuição Multinomial de Dirichlet.
O modelo pode ser expresso em equações como:
Nosso objetivo é estimar \(p_\text{leões}\), \(p_\text{tigres}\), \(p_\text{ursos}\) dado o vetor observado \(c = [c_{leões}, c_{tigres}, c_{ursos}]\)
Distribuição Multinomial
Este problema é um exemplo clássico da distribuição multinomial que descreve uma situação em que temos n tentativas independentes, cada uma com k resultados possíveis. Neste problema, \(n = 6\) e \(k = 3\). Caracterizado pela probabilidade de cada resultado, \( p_k \) que deve somar 1. Nosso objetivo é encontrar \( p_ \text {leões} \), \( p_ \text{tigres} \), \( p_ \text{ursos} \) dadas as observações \(c_{leões}= 3\), \(c_{tigres}= 2\) e \(c_{ursos}= 1\).
Distribuição Dirichlet
Uma distribuição multinomial com uma priori Dirichlet é chamada de modelo Dirichlet-Multinomial. A distribuição de Dirichlet é caracterizada por \( \alpha \), o vetor dos hiperparâmetros de concentração.
Hiperparâmetros
O vetor \(\alpha \) é o hiperparâmetro, um parâmetro de uma distribuição a priori.
O vetor hiperparâmetro pode ser considerado como pseudo-contagens, que usamos para mostrar nossa crença a priori para a prevalência de cada espécie. Se quisermos um hiperparâmetro uniforme refletindo que acreditamos que a chance de observar qualquer espécie é a mesma, definimos cada elemento de alfa igual, como \( \alpha = [1, 1, 1] \). Podemos aumentar ou diminuir o efeito das a prioris aumentando ou diminuindo os valores. Isso pode ser útil quando temos mais ou menos confiança em nossas crenças a prioris.
Instale os pacotes abaixo se necessário.
!pip install theano
!pip install arviz==0.15.1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: theano in /usr/local/lib/python3.10/dist-packages (1.0.5)
Requirement already satisfied: numpy>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from theano) (1.22.4)
Requirement already satisfied: scipy>=0.14 in /usr/local/lib/python3.10/dist-packages (from theano) (1.10.1)
Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from theano) (1.16.0)
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: arviz==0.15.1 in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.22.4)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.10.1)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (23.1)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (2022.12.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.1.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (0.5.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz==0.15.1) (3.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (4.39.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.4.4)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz==0.15.1) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz==0.15.1) (1.16.0)
import requests
import io
url="https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/codigos/utils.py"
leitura = requests.get(url)
utils = leitura.text
exec(utils)
import pandas as pd
import numpy as np
import arviz as az
# Visualizations
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')
plt.rcParams['font.size'] = 22
%matplotlib inline
from matplotlib import MatplotlibDeprecationWarning
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=MatplotlibDeprecationWarning)
import pymc as pm
Especificidades do problema#
Usaremos principalmente uma versão dos hiperparâmetros, \( \alpha = [1, 1, 1] \), que são os parâmetros da Dirichlet. Teste outros valores para ver como isso muda a solução do problema. Lembre-se que alterar os hiperparâmetros é inserir conhecimentos diferentes a respeito das distribuições a priori.
# observations
animals = ['lions', 'tigers', 'bears']
c = np.array([3, 2, 1])
# hyperparameters (initially all equal)
alphas = np.array([1, 1, 1])
alpha_list = [np.array([0.1, 0.1, 0.1]), np.array([1, 1, 1]),
np.array([5, 5, 5]), np.array([15, 15, 15])]
Expected Value#
Fonte: http://users.cecs.anu.edu.au/~ssanner/MLSS2010/Johnson1.pdf
Uma maneira de obter uma estimativa pontual da prevalência é usar o valor esperado da posteriori para \( p_k \). O valor esperado de uma Distribuição Multinomial de Dirichlet é: $\({\displaystyle \operatorname {E} [p_{i}\mid \mathbb {X} ,{\boldsymbol {\alpha }}]={\frac {c_{i}+\alpha _{i}}{N+\sum _{k}\alpha _{k}}}}\)$
display_probs(dict(zip(animals, (alphas + c) / (c.sum() + alphas.sum()))))
Species: lions Prevalence: 44.44%.
Species: tigers Prevalence: 33.33%.
Species: bears Prevalence: 22.22%.
Modelo Bayesiano#
Usaremos o PYMC3 e parâmetro \(\alpha = [1, 1, 1]\) para a priori. A verossimilhança é multinomial e a priori para os parâmetros é Dirichlet.
with pm.Model() as model:
# Parameters of the Multinomial are from a Dirichlet
parameters = pm.Dirichlet('parameters', a=alphas, shape=3)
# Observed data is from a Multinomial distribution
observed_data = pm.Multinomial(
'observed_data', n=6, p=parameters, shape=3, observed=c)
Amostrando do modelo#
A célula abaixo mostra 1000 amostras da posteriori em 2 cadeias. Usamos 500 amostras para ajuste que são descartadas. Isso significa que para cada variável aleatória do modelo - os parâmetros - teremos 2.000 valores retirados da distribuição posterior.
with model:
# Sample from the posterior
trace = pm.sample(draws=1000, chains=2, tune=500,
discard_tuned_samples=True)
summary = pm.summary(trace)
summary.index = animals
summary
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| lions | 0.441 | 0.152 | 0.150 | 0.710 | 0.004 | 0.003 | 1612.0 | 1290.0 | 1.0 |
| tigers | 0.338 | 0.148 | 0.075 | 0.605 | 0.004 | 0.003 | 1351.0 | 899.0 | 1.0 |
| bears | 0.221 | 0.132 | 0.017 | 0.459 | 0.003 | 0.002 | 1966.0 | 1269.0 | 1.0 |
Inspecionando os resultados#
Análise dos resultados com PYMC:
summary = az.summary(trace)
summary.index = animals
summary
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| lions | 0.441 | 0.152 | 0.150 | 0.710 | 0.004 | 0.003 | 1612.0 | 1290.0 | 1.0 |
| tigers | 0.338 | 0.148 | 0.075 | 0.605 | 0.004 | 0.003 | 1351.0 | 899.0 | 1.0 |
| bears | 0.221 | 0.132 | 0.017 | 0.459 | 0.003 | 0.002 | 1966.0 | 1269.0 | 1.0 |
Podemos perceber que a média das amostras está muito próxima do valor esperado, mas também devemos observar as estimativas intervalares.
Gráficos de diagnóstico#
#ax = pm.plot_posterior(trace, varnames = ['parameters'],
#figsize = (20, 10), edgecolor = 'k');
ax = az.plot_posterior(trace, figsize = (20, 10));
plt.rcParams['font.size'] = 22
for i, a in enumerate(animals):
ax[i].set_title(a);
Traceplot#
The traceplot shows a kernel density estimate (a smoothed histogram) on the left and all the samples that were drawn on the right. We collapse the chains on th plots (combined = True) but in reality we drew 2 independent chains.
Máximo a Posteriori usando PyMC#
with model:
# Find the maximum a posteriori estimate
map_ = pm.find_MAP()
display_probs(dict(zip(animals, map_['parameters'])))
Species: lions Prevalence: 50.00%.
Species: tigers Prevalence: 33.33%.
Species: bears Prevalence: 16.67%.
Próxima observação#
Para predizer uma próxima observação, extraímos uma única amostra 10.000 vezes de uma distribuição multinomial. A probabilidade de ver cada espécie é proporcional à obtida na amostragem.
# Draw from the multinomial
next_obs = np.random.multinomial(n = 1, pvals = [0.44,0.33,0.22], size = 10000)
# Data manipulation
next_obs = pd.melt(pd.DataFrame(next_obs, columns = ['Lions', 'Tigers', 'Bears'])).\
groupby('variable')['value'].\
value_counts(normalize=True).to_frame().\
rename(columns = {'value': 'total'}).reset_index()
next_obs = next_obs.loc[next_obs['value'] == 1]
# Bar plot
next_obs.set_index('variable')['total'].plot.bar(figsize = (8, 6));
plt.title('Next Observation Likelihood');
plt.ylabel('Likelihood'); plt.xlabel('');
next_obs.iloc[:, [0, 2]]
| variable | total | |
|---|---|---|
| 1 | Bears | 0.2258 |
| 3 | Lions | 0.4476 |
| 5 | Tigers | 0.3266 |
Qual a probabilidade do próximo animal ser um urso?
Se você tivesse que fazer uma previsão de qual seria o próximo animal observado, qual você escolheria?
Altere os hiperparâmetros para \(\alpha = [0.1, 0.1, 0.1]\) e \(\alpha = [15, 15, 15]\) e compare os resultados obtidos.
(Veja mais análises em https://github.com/WillKoehrsen/probabilistic-programming/blob/master/Estimating Probabilities with Bayesian Inference.ipynb)
Aula 7 - Modelos de regressão#
Programa#
Modelos lineares.
Regressão múltipla.
Regressão multivariada.
A qualidade do ajuste.
Seleção de modelos.
Análise de diagnóstico.
Referências:
Draper, N. R., & Smith, H. (1998). Applied regression analysis. 3rd edition. Wiley.
James, Gareth et al. (2013) An introduction to statistical learning. New York: Springer.
Dobson, A. J.; Barnett, Adrian G. (2018). An introduction to generalized linear models. CRC press.
Modelos de regressão#
Objetivos
Predizer \(Y\) a partir do conhecimento de variáveis em \(X = x\).
Em notação matricial, um modelo linear geral é dado por
em que
Y é a variável resposta (vetor de variáveis aleatórias observáveis),
X contém variáveis preditoras (matriz conhecida, ou seja, não-aleatória),
\(\beta\) é um vetor de parâmetros de interesse, que queremos estimar,
\(f\) é uma função das variáveis preditoras e dos parâmetros de interesse,
\(\epsilon\) é o erro aleatório (vetor de erros aleatórios não observáveis).
Modelos lineares#
Objetivos
Predizer \(Y\) a partir do conhecimento de variáveis em \(X = x\) utilizando uma função linear dos parâmetros \(\beta\).
Em notação matricial, um modelo linear geral é dado por
em que
Y é a variável resposta (vetor de variáveis aleatórias observáveis),
X contém variáveis preditoras (matriz conhecida, ou seja, não-aleatória),
\(\beta\) é um vetor de parâmetros de interesse, que queremos estimar,
\(\epsilon\) é o erro aleatório (vetor de erros aleatórios não observáveis).
Modelo de regressão linear simples#
Suponha que são observados os pares \((X_1,Y_1), \ldots, (X_n, Y_n)\) de variável preditora e resposta, respectivamente.
Um modelo linear simples para explicar a variabilidade de \(Y\) usando a variabilidade de \(X\) seria:
Nomenclatura:
\(Y_j\): \(j\)-ésima observação da variável resposta (dependente, aleatória),
\(\beta_0\) e \(\beta_1\): parâmetros desconhecidos e que queremos estimar (fixo e desconhecido),
\(X_j\): \(j\)-ésima observação da variável preditora (fixa, ou seja, não aleatória),
\(\epsilon_j\): \(j\)-ésimo erro aleatório não observável.
Suposições:
\(E(\epsilon_j)=0\) para \(j=1,\ldots,n\),
\(Var(\epsilon_j) = \sigma^2\) para \(j=1,\ldots,n\),
\(Cov(\epsilon_i,\epsilon_j)=0\) para \(i,j=1,\ldots,n\) e \(i\neq j\).
Interpretação dos parâmetros#
\(\beta_0\): valor esperado de \(Y\) quando \(X\) é zero.
\(\beta_1\): aumento esperado em \(Y\) quando \(X\) é acrescido de uma unidade.
Modelo de regressão linear múltipla#
Motivação: Deseja-se construir um modelo para explicar
\(Y_j\): valor de mercado de uma casa utilizando variáveis explicativas
\(X_{1j}\): área
\(X_{2j}\): localização
\(X_{3j}\): valor da casa no ano anterior
\(X_{4j}\): qualidade da construção
Um possível modelo linear (nos parâmetros) seria:
Nomenclatura:
\(Y_j\): variável resposta (dependente),
\(\beta_i\): parâmetros desconhecidos,
\(X_{ij}\): variáveis explicativas (covariáveis, variáveis independentes),
\(\epsilon_j\): erro aleatório.
Suposições:
\(E(\epsilon_j)=0\) para \( j=1,\ldots,n\),
\(Var(\epsilon_j) = \sigma^2\) para \( j=1,\ldots,n\),
\(Cov(\epsilon_i,\epsilon_j)=0\) para \( i,j=1,\ldots,n\) e \(i\neq j\).
Poderíamos estender esse modelo para \(p\) covariáveis,
Note que a variável resposta \(Y_i\) é unidimensional.
Poderíamos “empilhar” os dados de \(n\) indivíduos em linhas. Teríamos então matricialmente
ou seja,
Interpretação dos parâmetros#
\(\beta_0\): valor esperado de \(Y\) quando \(X_{1i}, X_{2i}, \ldots, X_{pi}\) são todas zero.
\(\beta_k\): aumento esperado em \(Y\) quando \(X_k\) é acrescido de uma unidade e todas as outras são mantidas fixadas, \(k=1,\ldots,p\).
Estimação dos parâmetros#
Alguns métodos podem ser usados para estimar os parâmetros, por exemplo
Método de mínimos quadrados ordinários (EMQ ou MQO)
Método de máxima verossimilhança (EMV)
No modelo linear geral $\(\large Y = X \beta + \epsilon. \)$
com as suposições
\(E( \epsilon) = {0}\),
\(Var( \epsilon) = \sigma^2 I\),
o estimador de mínimos quadrados que minimiza a soma de quadrados dos resíduos, é dado por
Se \(\epsilon\sim N({0},\sigma^2 I)\), então
o estimador de máxima verossimilhança de \(\beta\) é dado (também) por
Nesse caso,
e é comum estimar \(\sigma^2\) com
Observação
O EMV de \(\beta\) é não-viesado e consistente, que são boas propriedades estatísticas.
Valor ajustado de \(Y\)#
O valor ajustado de \(Y\), para um determinado \(X = x\) é obtido fazendo
O erro quadrático médio, MSE, é usado para estimar \(\sigma^2\), fazendo
Coeficiente de determinação do modelo#
O coeficiente de determinação, ou coeficiente de explicação do modelo, é dado por
em que \(SQT = Y^\top Y - \displaystyle\frac{1}{n}Y^\top \mathbb{1}^\top \mathbb{1} Y\), em que \(\mathbb{1}\) indica um vetor de uns de mesma dimensão de \(Y\).
Para levar em conta o aumento da explicação da variabilidade da resposta quando aumentamos o número de covariáveis, é comum considerar o coeficiente de determinação do modelo ajustado:
Tanto \(R^2\) quanto \(R^2_{ajustado}\) estão entre 0 e 1, e pode ser usado como um indício de qualidade do ajuste, quanto maior o coeficiente de determinação, melhor é o modelo linear.
Modelo de regressão linear multivariada#
Considere agora que, para cada indivíduo, sejam observadas \(m\) variáveis respostas, e que cada uma delas tenha uma relação linear com as p covariáveis.
Assim, teriamos \(m\) modelos de regressão:
Para cada um dos \(n\) indivíduos, vamos observar as \(m\) variáveis resposta e as \(p\) covariáveis. Assim, podemos definir um modelo de regressão multivariado
em que
Material complementar#
Para as suposições e demais desenvolvimentos no modelo de regressão linear multivariado, veja a aula de Regressão multivariada em https://youtu.be/9QIh71MQ2xQ
Análise de diagnóstico#
Os resíduos contém indicativos de adequabilidade das suposições do modelo
Os resíduos ordinários do modelo são dados por
É comum construir gráficos dos resíduos ordinários contra a ordem das observações, os valores ajustados \(\widehat{Y}\) e \(X_i\), para algumas das variáveis preditoras de interesse.
Espera-se que os resíduos sejam aleatoriamente distribuídos em torno de zero.
Alguns padrões de resíduos são ilustrados a seguir:
Existem algumas propostas para a padronização de resíduos:
Temos que
em que \(H = X (X ^{\top} X)^{-1} X ^{\top}\) é a matriz hat (matriz chapéu). Seja \(h_{ii}\) o \(i\)-ésimo elemento da diagonal de \(H\).
Pode-se mostrar que
Assim, podemos definir dois novos resíduos:
Resíduo Studentizado internamente:
em que \(s\) é uma estimativa de \(\sigma\), usualmente a raiz do MSE.
Resíduo Studentizado externamente
em que \(s(i)\) é uma estimativa para \(\sigma^2\) sem a observação i.
Observação: Com \(h_{ii}\) é possível identificar pontos de alavanca, que são outliers no espaço dos X e não necessariamente são ponto influentes, ou seja, não necessariamente mudam a inferência do modelo.
Existem técnicas para identificar pontos influentes, como DFFITS e Distância de Cook, que veremos na prática.
Ilustração de resíduos e pontos de alavanca#
Conjuntos de dados de Anscombe: https://pt.wikipedia.org/wiki/Quarteto_de_Anscombe
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
anscombe = sns.load_dataset("anscombe")
sns.lmplot(x="x", y="y", data=anscombe.query("dataset == 'IV'"),
robust=True, ci=None, scatter_kws={"s": 80});
# Gráficos de diagnóstico para o modelo linear simples com estimadores MQO
# Fonte: https://stackoverflow.com/questions/46607831/python-linear-regression-diagnostic-plots-similar-to-r
# Fonte: https://stackoverflow.com/questions/46304514/access-standardized-residuals-cooks-values-hatvalues-leverage-etc-easily-i/55764402#55764402
from matplotlib import pyplot as plt
from pandas.core.frame import DataFrame
import scipy.stats as stats
import statsmodels.api as sm
def linear_regression(df: DataFrame) -> DataFrame:
"""Perform a univariate regression and store results in a new data frame.
Args:
df (DataFrame): orginal data set with x and y.
Returns:
DataFrame: another dataframe with raw data and results.
"""
mod = sm.OLS(endog=df['y'], exog=df['x']).fit()
influence = mod.get_influence()
res = df.copy()
res['resid'] = mod.resid
res['fittedvalues'] = mod.fittedvalues
res['resid_std'] = mod.resid_pearson
res['leverage'] = influence.hat_matrix_diag
return res
def plot_diagnosis(df: DataFrame):
fig, axes = plt.subplots(nrows=2, ncols=2)
plt.style.use('seaborn')
# Residual against fitted values.
df.plot.scatter(
x='fittedvalues', y='resid', ax=axes[0, 0]
)
axes[0, 0].axhline(y=0, color='grey', linestyle='dashed')
axes[0, 0].set_xlabel('Fitted Values')
axes[0, 0].set_ylabel('Residuals')
axes[0, 0].set_title('Residuals vs Fitted')
# qqplot
sm.qqplot(
df['resid'], dist=stats.t, fit=True, line='45',
ax=axes[0, 1], c='#4C72B0'
)
axes[0, 1].set_title('Normal Q-Q')
# The scale-location plot.
df.plot.scatter(
x='fittedvalues', y='resid_std', ax=axes[1, 0]
)
axes[1, 0].axhline(y=0, color='grey', linestyle='dashed')
axes[1, 0].set_xlabel('Fitted values')
axes[1, 0].set_ylabel('Sqrt(|standardized residuals|)')
axes[1, 0].set_title('Scale-Location')
# Standardized residuals vs. leverage
df.plot.scatter(
x='leverage', y='resid_std', ax=axes[1, 1]
)
axes[1, 1].axhline(y=0, color='grey', linestyle='dashed')
axes[1, 1].set_xlabel('Leverage')
axes[1, 1].set_ylabel('Sqrt(|standardized residuals|)')
axes[1, 1].set_title('Residuals vs Leverage')
plt.tight_layout()
plt.show()
df = data=anscombe.query("dataset == 'IV'")
df = df.drop('dataset', axis=1)
df = linear_regression(df)
plot_diagnosis(df)
C:\Users\imikemori\Anaconda3\lib\site-packages\statsmodels\graphics\gofplots.py:1045: UserWarning: color is redundantly defined by the 'color' keyword argument and the fmt string "b" (-> color=(0.0, 0.0, 1.0, 1)). The keyword argument will take precedence.
ax.plot(x, y, fmt, **plot_style)
Seleção de modelos#
Algumas formas de avaliar e selecionar modelos, além da análise de diagnóstico, são
Validação com bases de treinamento e teste
Avaliação de métricas de qualidade do ajuste
Seleção de variáveis
Validação cruzada (K-fold Cross-Validation): ver https://scikit-learn.org/stable/modules/cross_validation.html
Métricas para avaliar a qualidade do ajuste#
Erro absoluto médio (EAM, MAE)#
O erro quadrático médio (EQM) ou mean absolute error (MAE) é a média do valor absoluto dos erros.
\(\mbox{MAE} = \displaystyle\frac{1}{n} \displaystyle\sum_{j=1}^{n}|y_j - \widehat{y}_j|\)
Erro quadrático médio (EQM, MSE)#
O erro quadrático médio (EQM) ou mean squared error (MSE) é a média dos erros quadráticos.
\(\mbox{MSE} = \displaystyle\frac{1}{n} \displaystyle\sum_{j=1}^{n}(y_j - \widehat{y}_j)^2\)
Raiz do erro quadrático médio (REQM, RMSE)#
A raiz do erro quadrático médio (REQM) ou root mean squared error (RMSE) é a raiz da média dos erros quadráticos.
\(\mbox{RMSE} = \displaystyle\sqrt{\frac{1}{n} \displaystyle\sum_{j=1}^{n}(y_j - \widehat{y}_j)^2}\)
Aplicação#
Suponha que desejamos predizer o valor de venda de uma casa utilizando variáveis preditoras como número de quartos, número de banheiros, tamanho da sala, número de andares, entre outras.
Fonte e alguns desenvolvimentos adicionais: ver https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices
!pip install folium
Collecting folium
Downloading folium-0.15.1-py2.py3-none-any.whl (97 kB)
---------------------------------------- 97.0/97.0 kB 2.7 MB/s eta 0:00:00
Collecting xyzservices
Downloading xyzservices-2023.10.1-py3-none-any.whl (56 kB)
---------------------------------------- 56.3/56.3 kB 2.9 MB/s eta 0:00:00
Collecting branca>=0.6.0
Downloading branca-0.7.0-py3-none-any.whl (25 kB)
Requirement already satisfied: numpy in c:\users\imikemori\anaconda3\lib\site-packages (from folium) (1.24.4)
Requirement already satisfied: jinja2>=2.9 in c:\users\imikemori\anaconda3\lib\site-packages (from folium) (2.11.3)
Requirement already satisfied: requests in c:\users\imikemori\anaconda3\lib\site-packages (from folium) (2.28.1)
Requirement already satisfied: MarkupSafe>=0.23 in c:\users\imikemori\anaconda3\lib\site-packages (from jinja2>=2.9->folium) (2.0.1)
Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (2.0.4)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (2022.9.14)
Requirement already satisfied: idna<4,>=2.5 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (3.3)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (1.26.11)
Installing collected packages: xyzservices, branca, folium
Successfully installed branca-0.7.0 folium-0.15.1 xyzservices-2023.10.1
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import linear_model
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn import metrics
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
import seaborn as sns
from mpl_toolkits.mplot3d import Axes3D
import folium
from folium.plugins import HeatMap
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
evaluation = pd.DataFrame({'Model': [],
'Details':[],
'Root Mean Squared Error (RMSE)':[],
'R-squared (training)':[],
'Adjusted R-squared (training)':[],
'R-squared (test)':[],
'Adjusted R-squared (test)':[],
'5-Fold Cross Validation':[]})
# Faça a leitura dos dados localmente
#df = pd.read_csv('/hdd/MBA/ECD/Data/kc_house_data.csv')
# Ou faça a leitura direto do github
df = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/kc_house_data.csv')
df.head()
| id | date | price | bedrooms | bathrooms | sqft_living | sqft_lot | floors | waterfront | view | ... | grade | sqft_above | sqft_basement | yr_built | yr_renovated | zipcode | lat | long | sqft_living15 | sqft_lot15 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7129300520 | 20141013T000000 | 221900.0 | 3 | 1.00 | 1180 | 5650 | 1.0 | 0 | 0 | ... | 7 | 1180.0 | 0 | 1955 | 0 | 98178 | 47.5112 | -122.257 | 1340 | 5650 |
| 1 | 6414100192 | 20141209T000000 | 538000.0 | 3 | 2.25 | 2570 | 7242 | 2.0 | 0 | 0 | ... | 7 | 2170.0 | 400 | 1951 | 1991 | 98125 | 47.7210 | -122.319 | 1690 | 7639 |
| 2 | 5631500400 | 20150225T000000 | 180000.0 | 2 | 1.00 | 770 | 10000 | 1.0 | 0 | 0 | ... | 6 | 770.0 | 0 | 1933 | 0 | 98028 | 47.7379 | -122.233 | 2720 | 8062 |
| 3 | 2487200875 | 20141209T000000 | 604000.0 | 4 | 3.00 | 1960 | 5000 | 1.0 | 0 | 0 | ... | 7 | 1050.0 | 910 | 1965 | 0 | 98136 | 47.5208 | -122.393 | 1360 | 5000 |
| 4 | 1954400510 | 20150218T000000 | 510000.0 | 3 | 2.00 | 1680 | 8080 | 1.0 | 0 | 0 | ... | 8 | 1680.0 | 0 | 1987 | 0 | 98074 | 47.6168 | -122.045 | 1800 | 7503 |
5 rows × 21 columns
df.describe()
| id | price | bedrooms | bathrooms | sqft_living | sqft_lot | floors | waterfront | view | condition | grade | sqft_above | sqft_basement | yr_built | yr_renovated | zipcode | lat | long | sqft_living15 | sqft_lot15 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 2.161300e+04 | 2.161300e+04 | 21613.000000 | 21613.000000 | 21613.000000 | 2.161300e+04 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21611.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 | 21613.000000 |
| mean | 4.580302e+09 | 5.400881e+05 | 3.370842 | 2.114757 | 2079.899736 | 1.510697e+04 | 1.494309 | 0.007542 | 0.234303 | 3.409430 | 7.656873 | 1788.396095 | 291.509045 | 1971.005136 | 84.402258 | 98077.939805 | 47.560053 | -122.213896 | 1986.552492 | 12768.455652 |
| std | 2.876566e+09 | 3.671272e+05 | 0.930062 | 0.770163 | 918.440897 | 4.142051e+04 | 0.539989 | 0.086517 | 0.766318 | 0.650743 | 1.175459 | 828.128162 | 442.575043 | 29.373411 | 401.679240 | 53.505026 | 0.138564 | 0.140828 | 685.391304 | 27304.179631 |
| min | 1.000102e+06 | 7.500000e+04 | 0.000000 | 0.000000 | 290.000000 | 5.200000e+02 | 1.000000 | 0.000000 | 0.000000 | 1.000000 | 1.000000 | 290.000000 | 0.000000 | 1900.000000 | 0.000000 | 98001.000000 | 47.155900 | -122.519000 | 399.000000 | 651.000000 |
| 25% | 2.123049e+09 | 3.219500e+05 | 3.000000 | 1.750000 | 1427.000000 | 5.040000e+03 | 1.000000 | 0.000000 | 0.000000 | 3.000000 | 7.000000 | 1190.000000 | 0.000000 | 1951.000000 | 0.000000 | 98033.000000 | 47.471000 | -122.328000 | 1490.000000 | 5100.000000 |
| 50% | 3.904930e+09 | 4.500000e+05 | 3.000000 | 2.250000 | 1910.000000 | 7.618000e+03 | 1.500000 | 0.000000 | 0.000000 | 3.000000 | 7.000000 | 1560.000000 | 0.000000 | 1975.000000 | 0.000000 | 98065.000000 | 47.571800 | -122.230000 | 1840.000000 | 7620.000000 |
| 75% | 7.308900e+09 | 6.450000e+05 | 4.000000 | 2.500000 | 2550.000000 | 1.068800e+04 | 2.000000 | 0.000000 | 0.000000 | 4.000000 | 8.000000 | 2210.000000 | 560.000000 | 1997.000000 | 0.000000 | 98118.000000 | 47.678000 | -122.125000 | 2360.000000 | 10083.000000 |
| max | 9.900000e+09 | 7.700000e+06 | 33.000000 | 8.000000 | 13540.000000 | 1.651359e+06 | 3.500000 | 1.000000 | 4.000000 | 5.000000 | 13.000000 | 9410.000000 | 4820.000000 | 2015.000000 | 2015.000000 | 98199.000000 | 47.777600 | -121.315000 | 6210.000000 | 871200.000000 |
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(df.drop(['id'], axis=1).corr(),annot=True, square=True, cmap="BuGn")
plt.title('Matriz de correlações de Pearson',fontsize=25)
Text(0.5, 1.0, 'Matriz de correlações de Pearson')
Modelo linear simples#
from statsmodels.formula.api import ols
#Ajusta o modelo de regressão linear simples para o preço das casas
mod = ols('price~sqft_living',data=df)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: price R-squared: 0.493
Model: OLS Adj. R-squared: 0.493
Method: Least Squares F-statistic: 2.100e+04
Date: Wed, 17 Jan 2024 Prob (F-statistic): 0.00
Time: 01:54:36 Log-Likelihood: -3.0027e+05
No. Observations: 21613 AIC: 6.005e+05
Df Residuals: 21611 BIC: 6.006e+05
Df Model: 1
Covariance Type: nonrobust
===============================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------
Intercept -4.358e+04 4402.690 -9.899 0.000 -5.22e+04 -3.5e+04
sqft_living 280.6236 1.936 144.920 0.000 276.828 284.419
==============================================================================
Omnibus: 14832.490 Durbin-Watson: 1.983
Prob(Omnibus): 0.000 Jarque-Bera (JB): 546444.713
Skew: 2.824 Prob(JB): 0.00
Kurtosis: 26.977 Cond. No. 5.63e+03
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 5.63e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
Modelo ajustado
\(\hat{Y}_i = -43580 + 280.62 \mbox{ sqft_living}\)
Modelo linear múltiplo#
X = df[['bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront','view','condition','grade']].values
y = df['price'].values
from statsmodels.formula.api import ols
#Ajusta o modelo de regressão linear múltipla para o preço das casas
modelo = ols('price ~ bedrooms + bathrooms + sqft_living + sqft_lot + floors + waterfront + view + condition + grade',data=df)
res = modelo.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: price R-squared: 0.605
Model: OLS Adj. R-squared: 0.605
Method: Least Squares F-statistic: 3673.
Date: Wed, 17 Jan 2024 Prob (F-statistic): 0.00
Time: 01:54:36 Log-Likelihood: -2.9757e+05
No. Observations: 21613 AIC: 5.952e+05
Df Residuals: 21603 BIC: 5.952e+05
Df Model: 9
Covariance Type: nonrobust
===============================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------
Intercept -6.827e+05 1.73e+04 -39.509 0.000 -7.17e+05 -6.49e+05
bedrooms -3.367e+04 2159.240 -15.594 0.000 -3.79e+04 -2.94e+04
bathrooms -1.142e+04 3449.874 -3.309 0.001 -1.82e+04 -4653.384
sqft_living 196.3657 3.454 56.855 0.000 189.596 203.135
sqft_lot -0.3462 0.039 -8.931 0.000 -0.422 -0.270
floors -1.312e+04 3575.901 -3.669 0.000 -2.01e+04 -6109.502
waterfront 5.783e+05 1.99e+04 29.128 0.000 5.39e+05 6.17e+05
view 6.327e+04 2348.558 26.939 0.000 5.87e+04 6.79e+04
condition 5.499e+04 2521.782 21.805 0.000 5e+04 5.99e+04
grade 1.006e+05 2231.983 45.064 0.000 9.62e+04 1.05e+05
==============================================================================
Omnibus: 15533.601 Durbin-Watson: 1.983
Prob(Omnibus): 0.000 Jarque-Bera (JB): 900296.321
Skew: 2.871 Prob(JB): 0.00
Kurtosis: 34.093 Cond. No. 5.59e+05
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 5.59e+05. This might indicate that there are
strong multicollinearity or other numerical problems.
Métricas#
# R2 ajustado de
# https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices
def adjustedR2(r2,n,k):
return r2-(k-1)/(n-k)*(1-r2)
# Fonte: https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices
train_data,test_data = train_test_split(df,train_size = 0.8,random_state=3)
features = ['bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront', 'view', 'condition', 'grade']
complex_model_1 = linear_model.LinearRegression()
complex_model_1.fit(train_data[features],train_data['price'])
print('Intercept: {}'.format(complex_model_1.intercept_))
print('Coefficients: {}'.format(complex_model_1.coef_))
pred = complex_model_1.predict(test_data[features])
rmsecm = float(format(np.sqrt(metrics.mean_squared_error(test_data['price'],pred)),'.3f'))
rtrcm = float(format(complex_model_1.score(train_data[features],train_data['price']),'.3f'))
artrcm = float(format(adjustedR2(complex_model_1.score(train_data[features],train_data['price']),train_data.shape[0],len(features)),'.3f'))
rtecm = float(format(complex_model_1.score(test_data[features],test_data['price']),'.3f'))
artecm = float(format(adjustedR2(complex_model_1.score(test_data[features],test_data['price']),test_data.shape[0],len(features)),'.3f'))
cv = float(format(cross_val_score(complex_model_1,df[features],df['price'],cv=5).mean(),'.3f'))
r = evaluation.shape[0]
evaluation.loc[r] = ['Multiple Regression-1','selected features',rmsecm,rtrcm,artrcm,rtecm,artecm,cv]
evaluation.sort_values(by = '5-Fold Cross Validation', ascending=False)
Intercept: -682602.5354521422
Coefficients: [-3.37785908e+04 -1.09025075e+04 1.99448654e+02 -3.50854472e-01
-1.48240739e+04 5.45051297e+05 6.50285793e+04 5.58998443e+04
9.95878298e+04]
| Model | Details | Root Mean Squared Error (RMSE) | R-squared (training) | Adjusted R-squared (training) | R-squared (test) | Adjusted R-squared (test) | 5-Fold Cross Validation | |
|---|---|---|---|---|---|---|---|---|
| 0 | Multiple Regression-1 | selected features | 221680.867 | 0.602 | 0.602 | 0.617 | 0.616 | 0.601 |
Seleção de variáveis (feature selection)#
X = df[['bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront','view','condition','grade']]
y = df['price']
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.2, random_state = 1)
# https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices
# load and summarize the dataset
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
from sklearn.feature_selection import mutual_info_regression
from matplotlib import pyplot
# feature selection
def select_features(X_treino, y_treino, X_teste):
# configure to select all features
fs = SelectKBest(score_func=mutual_info_regression, k='all')
# learn relationship from treinoing data
fs.fit(X_treino, y_treino)
# transform treino input data
X_treino_fs = fs.transform(X_treino)
# transform teste input data
X_teste_fs = fs.transform(X_teste)
return X_treino_fs, X_teste_fs, fs
# split into treino and teste sets
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.2, random_state=1)
# feature selection
X_treino_fs, X_teste_fs, fs = select_features(X_treino, y_treino, X_teste)
# what are scores for the features
for i in range(len(fs.scores_)):
print('Feature %d: %f' % (i, fs.scores_[i]))
# plot the scores
pyplot.bar([i for i in range(len(fs.scores_))], fs.scores_)
pyplot.show()
Feature 0: 0.072447
Feature 1: 0.204750
Feature 2: 0.348548
Feature 3: 0.063075
Feature 4: 0.075901
Feature 5: 0.014150
Feature 6: 0.058074
Feature 7: 0.009088
Feature 8: 0.339902
# Fonte: https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices
train_data,test_data = train_test_split(df,train_size = 0.8,random_state=3)
features = ['sqft_living','grade']
complex_model_1 = linear_model.LinearRegression()
complex_model_1.fit(train_data[features],train_data['price'])
print('Intercept: {}'.format(complex_model_1.intercept_))
print('Coefficients: {}'.format(complex_model_1.coef_))
pred = complex_model_1.predict(test_data[features])
rmsecm = float(format(np.sqrt(metrics.mean_squared_error(test_data['price'],pred)),'.3f'))
rtrcm = float(format(complex_model_1.score(train_data[features],train_data['price']),'.3f'))
artrcm = float(format(adjustedR2(complex_model_1.score(train_data[features],train_data['price']),train_data.shape[0],len(features)),'.3f'))
rtecm = float(format(complex_model_1.score(test_data[features],test_data['price']),'.3f'))
artecm = float(format(adjustedR2(complex_model_1.score(test_data[features],test_data['price']),test_data.shape[0],len(features)),'.3f'))
cv = float(format(cross_val_score(complex_model_1,df[features],df['price'],cv=5).mean(),'.3f'))
r = evaluation.shape[0]
evaluation.loc[r] = ['Multiple Regression-3','selected features',rmsecm,rtrcm,artrcm,rtecm,artecm,cv]
evaluation.sort_values(by = '5-Fold Cross Validation', ascending=False)
Intercept: -598022.8027257536
Coefficients: [ 186.877963 97878.7708715]
| Model | Details | Root Mean Squared Error (RMSE) | R-squared (training) | Adjusted R-squared (training) | R-squared (test) | Adjusted R-squared (test) | 5-Fold Cross Validation | |
|---|---|---|---|---|---|---|---|---|
| 0 | Multiple Regression-1 | selected features | 221680.867 | 0.602 | 0.602 | 0.617 | 0.616 | 0.601 |
| 1 | Multiple Regression-3 | selected features | 242366.385 | 0.533 | 0.533 | 0.542 | 0.542 | 0.533 |
Análise de resíduos#
# Ajusta o modelo de regressão linear múltipla para o preço das casas com duas preditoras
modelo = ols('price ~ sqft_living + grade',data=df)
res = modelo.fit()
# valores ajustados de E(Y)
ypred=res.fittedvalues
# resíduo=observado-ajustado
residuo = res.resid
# objeto para a análise de pontos influentes
infl = res.get_influence()
# diagonal da matriz hat
hii = infl.hat_matrix_diag
# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal
# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external
# DFFITS
(dffits,p) = infl.dffits
# Distância de Cook
(cook,p) = infl.cooks_distance
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')
for ax in fig.get_axes():
ax.label_outer()
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(df.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
#ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(df.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(df.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')
for ax in fig.get_axes():
ax.label_outer()
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
Transformação em Y#
Transformação de Box Cox: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.boxcox.html
stats.boxcox(df['price'])
(array([4.0334758 , 4.07834265, 4.02144527, ..., 4.06460556, 4.06434971,
4.05395248]),
-0.23401853749997595)
df['price_transformado'] = (pow(df['price'],-0.234) - 1)/(-0.234)
# Ajusta o modelo de regressão linear múltipla para o preço das casas com duas preditoras
modelo = ols('price_transformado ~ sqft_living + grade',data=df)
res = modelo.fit()
# valores preditos de E(Y)
ypred=res.fittedvalues
# resíduo=observado-ajustado
residuo = res.resid
# objeto para a análise de pontos influentes
infl = res.get_influence()
# diagonal da matriz hat
hii = infl.hat_matrix_diag
# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal
# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external
# DFFITS
(dffits,p) = infl.dffits
# Distância de Cook
(cook,p) = infl.cooks_distance
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
#ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')
for ax in fig.get_axes():
ax.label_outer()
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(df.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
#ax2.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(df.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(df.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')
for ax in fig.get_axes():
ax.label_outer()
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
#!pip install plotly
# x and y given as DataFrame columns
import plotly.express as px
fig = px.scatter(x = df.index, y=cook)
fig.show()
df['grade'].describe()
count 21613.000000
mean 7.656873
std 1.175459
min 1.000000
25% 7.000000
50% 7.000000
75% 8.000000
max 13.000000
Name: grade, dtype: float64
df.iloc[12777,:]
#2.280.000
id 1225069038
date 20140505T000000
price 2280000.0
bedrooms 7
bathrooms 8.0
sqft_living 13540
sqft_lot 307752
floors 3.0
waterfront 0
view 4
condition 3
grade 12
sqft_above 9410.0
sqft_basement 4130
yr_built 1999
yr_renovated 0
zipcode 98053
lat 47.6675
long -121.986
sqft_living15 4850
sqft_lot15 217800
price_transformado 4.1345
Name: 12777, dtype: object
sns.boxplot(df['sqft_living'])
<AxesSubplot:xlabel='sqft_living'>
px.scatter(y = df['price'], x=df['sqft_living'])
px.scatter(y = df['price_transformado'], x=df['sqft_living'])
res.params
Intercept 3.983499
sqft_living 0.000009
grade 0.008750
dtype: float64
res.predict()
array([4.055955 , 4.06915421, 4.04331154, ..., 4.05443567, 4.06869342,
4.05443567])
X = df[['sqft_living', 'grade']].values.reshape(-1,2)
Y = df['price']
x = X[:, 0]
y = X[:, 1]
z = Y
# Visualização do modelo de regressão múltipla em Python
## Fonte: https://aegis4048.github.io/mutiple_linear_regression_and_visualization_in_python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from mpl_toolkits.mplot3d import Axes3D
######################################## Preparação dos dados #########################################
X = df[['sqft_living', 'grade']].values.reshape(-1,2)
Y = df['price_transformado']
######################## Preparação para a visualização ###############################
x = X[:, 0]
y = X[:, 1]
z = Y
x_pred = np.linspace(290, 13540, 30) # grade de valores para x
y_pred = np.linspace(1, 13, 30) # grade de valores para y
xx_pred, yy_pred = np.meshgrid(x_pred, y_pred)
model_viz = np.array([xx_pred.flatten(), yy_pred.flatten()]).T
################################################ Treinamento do modelo #############################################
ols = linear_model.LinearRegression()
model = ols.fit(X, Y)
predicted = model.predict(model_viz)
############################################## Avaliação ############################################
r2 = model.score(X, Y)
############################################## Plota o gráfico ################################################
plt.style.use('default')
fig = plt.figure(figsize=(12, 4))
ax1 = fig.add_subplot(131, projection='3d')
ax2 = fig.add_subplot(132, projection='3d')
axes = [ax1, ax2]
for ax in axes:
ax.plot(x, y, z, color='k', zorder=15, linestyle='none', marker='o', alpha=0.5)
ax.scatter(xx_pred.flatten(), yy_pred.flatten(), predicted, facecolor=(0,0,0,0), s=20, edgecolor='#70b3f0')
ax.set_xlabel('sqft_living', fontsize=12)
ax.set_ylabel('grade', fontsize=12)
ax.set_zlabel('price_transformado', fontsize=12)
ax.locator_params(nbins=4, axis='x')
ax.locator_params(nbins=5, axis='x')
ax1.view_init(elev=20, azim=-10)
ax2.view_init(elev=0, azim=290)
fig.suptitle('$R^2 = %.2f$' % r2, fontsize=20)
fig.tight_layout()
for ii in np.arange(0, 180, 1):
ax1.view_init(elev=20, azim=(2*ii))
ax2.view_init(elev=0, azim=(2*ii))
fig.savefig('gif_image%d.png' % ii)
Métricas com bases de treino e teste#
# Ajusta o modelo de regressão linear múltipla para o preço das casas com duas preditoras
from statsmodels.formula.api import ols
modelo = ols('price ~ sqft_living + grade', data=train_data)
res = modelo.fit()
previsao = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_absolute_error(previsao, y_teste)
168694.27817451468
mean_squared_error(previsao, y_teste)
79633647247.60545
Exercício
É possível melhorar esse modelo?
Testar outros métodos para a seleção de variáveis.
Prática 1#
Modelos Lineares#
Exercício: dados Prestige#
Fontes:
https://rstudio-pubs-static.s3.amazonaws.com/63531_55f83f76f1104efd9b6d694f9228d55e.html
Estatística para Ciência de Dados. Notas de aula da Profa. Mariana Cúri.
O arquivo Prestige.csv contém 102 observações e 7 variáveis. A descrição das variáveis no conjunto de dados é a seguinte:
occupation: profissão
education: número médio de anos de estudo dos titulares da profissão.
income: A renda média dos ocupantes ocupacionais, em dólares.
women: a porcentagem de mulheres na ocupação.
prestige: a classificação média de prestígio para a ocupação.
census: o código da ocupação utilizado na pesquisa.
type: profissional e gerencial (prof), colarinho branco (wc), colarinho azul (bc) ou ausente (NA) (Fox e Weisberg 2011)
Ajuste um modelo de regressão linear múltiplo para compreender a associação entre o prestígio e as preditoras renda, educação e mulheres. Verifique se o modelo é adequado.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
import statistics
import pandas as pd
from scipy import stats
from scipy.stats import norm
# Dados Prestige - Leitura dos dados a partir de uma pasta local
#pkgdir = '/hdd/MBA/ECD/Data'
#dados = pd.read_csv(f'{pkgdir}/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
# Leitura dos dados direto do github
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
dados.head()
| occupation | education | income | women | prestige | census | type | |
|---|---|---|---|---|---|---|---|
| 0 | gov.administrators | 13.11 | 12351 | 11.16 | 68.8 | 1113 | prof |
| 1 | general.managers | 12.26 | 25879 | 4.02 | 69.1 | 1130 | prof |
| 2 | accountants | 12.77 | 9271 | 15.70 | 63.4 | 1171 | prof |
| 3 | purchasing.officers | 11.42 | 8865 | 9.11 | 56.8 | 1175 | prof |
| 4 | chemists | 14.62 | 8403 | 11.68 | 73.5 | 2111 | prof |
dados.describe()
| education | income | women | prestige | census | |
|---|---|---|---|---|---|
| count | 102.000000 | 102.000000 | 102.000000 | 102.000000 | 102.000000 |
| mean | 10.738039 | 6797.901961 | 28.979020 | 46.833333 | 5401.774510 |
| std | 2.728444 | 4245.922227 | 31.724931 | 17.204486 | 2644.993215 |
| min | 6.380000 | 611.000000 | 0.000000 | 14.800000 | 1113.000000 |
| 25% | 8.445000 | 4106.000000 | 3.592500 | 35.225000 | 3120.500000 |
| 50% | 10.540000 | 5930.500000 | 13.600000 | 43.600000 | 5135.000000 |
| 75% | 12.647500 | 8187.250000 | 52.202500 | 59.275000 | 8312.500000 |
| max | 15.970000 | 25879.000000 | 97.510000 | 87.200000 | 9517.000000 |
# Constrói os gráficos de dispersão
plt.scatter(x=dados.education,y=dados.prestige)
plt.xlabel("anos de educação")
plt.ylabel("prestígio da ocupação")
plt.show()
import seaborn as sns
dados1 = dados[["education","income","women","prestige","type"]]
sns.pairplot(dados, kind='reg')
<seaborn.axisgrid.PairGrid at 0x1f5e9aa7670>
# Outra maneira, diferenciando por "type"
sns.pairplot(dados1, hue='type', kind='reg')
<seaborn.axisgrid.PairGrid at 0x1f58177a760>
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(dados1.corr(),annot=True, square=True, cmap="BuGn")
plt.title('Matriz de correlações de Pearson',fontsize=25)
Text(0.5, 1.0, 'Matriz de correlações de Pearson')
from statsmodels.formula.api import ols
#Ajusta o modelo de regressão linear múltipla com Prestige como variável resposta
mod = ols('dados.prestige ~ dados.income + dados.education + dados.women',data=dados)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: dados.prestige R-squared: 0.798
Model: OLS Adj. R-squared: 0.792
Method: Least Squares F-statistic: 129.2
Date: Wed, 17 Jan 2024 Prob (F-statistic): 6.26e-34
Time: 01:55:40 Log-Likelihood: -352.82
No. Observations: 102 AIC: 713.6
Df Residuals: 98 BIC: 724.1
Df Model: 3
Covariance Type: nonrobust
===================================================================================
coef std err t P>|t| [0.025 0.975]
-----------------------------------------------------------------------------------
Intercept -6.7943 3.239 -2.098 0.039 -13.222 -0.366
dados.income 0.0013 0.000 4.729 0.000 0.001 0.002
dados.education 4.1866 0.389 10.771 0.000 3.415 4.958
dados.women -0.0089 0.030 -0.293 0.770 -0.069 0.051
==============================================================================
Omnibus: 0.271 Durbin-Watson: 1.687
Prob(Omnibus): 0.873 Jarque-Bera (JB): 0.436
Skew: -0.085 Prob(JB): 0.804
Kurtosis: 2.729 Cond. No. 3.35e+04
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 3.35e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
# Estimativa da variância do erro (sigma2), que é o MSE=SQE/(n-p)
res.mse_resid
61.56704276635243
# valores preditos de E(Y)
ypred=res.fittedvalues
# resíduo=observado-ajustado
residuo = res.resid
# objeto para a análise de pontos influentes
infl = res.get_influence()
# diagonal da matriz hat
hii = infl.hat_matrix_diag
# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal
# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external
# DFFITS
(dffits,p) = infl.dffits
# Distância de Cook
(cook,p) = infl.cooks_distance
Elabora os gráficos de todos os resíduos
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')
for ax in fig.get_axes():
ax.label_outer()
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(dados.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(dados.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(dados.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')
for ax in fig.get_axes():
ax.label_outer()
Identificando a observações que se destacam das demais em alguns gráficos de diagnóstico
# Instale a plotly se necessário
#!pip install plotly
import plotly.express as px
fig = px.scatter(x = dados.index, y=cook)
fig.show()
Identificadas as observações 1 e, em menor escala, a 52. Verifique o que elas tem de especial.
# x and y given as DataFrame columns
import plotly.express as px
fig = px.scatter(x = dados.index, y=hii)
fig.show()
Identificadas as observações 1 e 23. Verifique o que elas tem de especial.
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
# Os autores em https://rstudio-pubs-static.s3.amazonaws.com/63531_55f83f76f1104efd9b6d694f9228d55e.html
# sugerem a transformação logaritmica da variável income e verificamos se type deve ser adicionada
# Modelo de regressão com a variável "income" transformada
l_income = np.log2(dados.income) # base 2
le_income = np.log(dados.income) # base e
mod = ols('dados.prestige ~ l_income + dados.education + dados.women',data=dados)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: dados.prestige R-squared: 0.835
Model: OLS Adj. R-squared: 0.830
Method: Least Squares F-statistic: 165.4
Date: Wed, 17 Jan 2024 Prob (F-statistic): 3.21e-38
Time: 01:55:42 Log-Likelihood: -342.51
No. Observations: 102 AIC: 693.0
Df Residuals: 98 BIC: 703.5
Df Model: 3
Covariance Type: nonrobust
===================================================================================
coef std err t P>|t| [0.025 0.975]
-----------------------------------------------------------------------------------
Intercept -110.9658 14.843 -7.476 0.000 -140.421 -81.511
l_income 9.3147 1.327 7.022 0.000 6.682 11.947
dados.education 3.7305 0.354 10.527 0.000 3.027 4.434
dados.women 0.0469 0.030 1.568 0.120 -0.012 0.106
==============================================================================
Omnibus: 0.221 Durbin-Watson: 1.828
Prob(Omnibus): 0.895 Jarque-Bera (JB): 0.120
Skew: 0.084 Prob(JB): 0.942
Kurtosis: 2.989 Cond. No. 941.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Modelo de regressão com a variável "income" transformada e variável type
mod = ols('dados.prestige ~ l_income+dados.education+dados.women+dados.type',data=dados)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: dados.prestige R-squared: 0.865
Model: OLS Adj. R-squared: 0.858
Method: Least Squares F-statistic: 118.3
Date: Wed, 17 Jan 2024 Prob (F-statistic): 1.71e-38
Time: 01:55:42 Log-Likelihood: -318.49
No. Observations: 98 AIC: 649.0
Df Residuals: 92 BIC: 664.5
Df Model: 5
Covariance Type: nonrobust
======================================================================================
coef std err t P>|t| [0.025 0.975]
--------------------------------------------------------------------------------------
Intercept -115.6722 18.802 -6.152 0.000 -153.014 -78.330
dados.type[T.prof] 5.2919 3.556 1.488 0.140 -1.770 12.354
dados.type[T.wc] -3.2160 2.407 -1.336 0.185 -7.996 1.564
l_income 10.1582 1.602 6.340 0.000 6.976 13.340
dados.education 2.9738 0.602 4.940 0.000 1.778 4.170
dados.women 0.0838 0.032 2.601 0.011 0.020 0.148
==============================================================================
Omnibus: 0.002 Durbin-Watson: 1.953
Prob(Omnibus): 0.999 Jarque-Bera (JB): 0.100
Skew: -0.006 Prob(JB): 0.951
Kurtosis: 2.844 Cond. No. 1.28e+03
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.28e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
# Modelo de regressão final?
# Vamos exlcuir type com base no alto valor p, mas ela pode ser incluída em interação com income, veja a próxima célula
# Que outros critérios você utilizaria para a seleção de variáveis?
mod = ols('dados.prestige ~ l_income+dados.education',data=dados)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: dados.prestige R-squared: 0.831
Model: OLS Adj. R-squared: 0.828
Method: Least Squares F-statistic: 243.3
Date: Wed, 17 Jan 2024 Prob (F-statistic): 6.11e-39
Time: 01:55:42 Log-Likelihood: -343.78
No. Observations: 102 AIC: 693.6
Df Residuals: 99 BIC: 701.4
Df Model: 2
Covariance Type: nonrobust
===================================================================================
coef std err t P>|t| [0.025 0.975]
-----------------------------------------------------------------------------------
Intercept -95.1940 10.998 -8.656 0.000 -117.016 -73.372
l_income 7.9278 0.996 7.959 0.000 5.951 9.904
dados.education 4.0020 0.312 12.846 0.000 3.384 4.620
==============================================================================
Omnibus: 0.092 Durbin-Watson: 1.886
Prob(Omnibus): 0.955 Jarque-Bera (JB): 0.121
Skew: 0.067 Prob(JB): 0.941
Kurtosis: 2.897 Cond. No. 260.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Análise extra: inclusão de interação de variáveis
# Modelo de regressão com interação
mod = ols('dados.prestige ~ dados.type*(dados.income)',data=dados)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: dados.prestige R-squared: 0.829
Model: OLS Adj. R-squared: 0.819
Method: Least Squares F-statistic: 88.94
Date: Wed, 17 Jan 2024 Prob (F-statistic): 1.08e-33
Time: 01:55:42 Log-Likelihood: -330.34
No. Observations: 98 AIC: 672.7
Df Residuals: 92 BIC: 688.2
Df Model: 5
Covariance Type: nonrobust
===================================================================================================
coef std err t P>|t| [0.025 0.975]
---------------------------------------------------------------------------------------------------
Intercept 13.9045 3.167 4.390 0.000 7.614 20.195
dados.type[T.prof] 45.0190 4.291 10.492 0.000 36.497 53.541
dados.type[T.wc] 18.9807 5.342 3.553 0.001 8.371 29.591
dados.income 0.0040 0.001 7.276 0.000 0.003 0.005
dados.type[T.prof]:dados.income -0.0032 0.001 -5.256 0.000 -0.004 -0.002
dados.type[T.wc]:dados.income -0.0022 0.001 -2.238 0.028 -0.004 -0.000
==============================================================================
Omnibus: 8.833 Durbin-Watson: 1.932
Prob(Omnibus): 0.012 Jarque-Bera (JB): 8.738
Skew: 0.611 Prob(JB): 0.0127
Kurtosis: 3.805 Cond. No. 8.76e+04
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 8.76e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
import itertools
##### Entendendo o que é a interação
# Veja que as retas de regressão são bastante diferentes para diferentes types
x = np.linspace(0,25000)
y1 = 13 + 0.004*x # blue collar
y2 =(13+18.98) + (0.004-0.0022)*x # white collar
y3 = (13+45) + (0.004-0.0032)*x # prof
plt.plot(x, y1, label="blue collar", color="blue")
plt.plot(x, y2, label="white collar", color="gray")
plt.plot(x, y3, label="prof",color="red")
colors = itertools.cycle(["b", "r", "gray"])
groups = dados.groupby("type")
for name, group in groups:
plt.plot(group["income"], group["prestige"], marker="o", linestyle="", label=name, color=next(colors))
plt.legend()
plt.xlabel("income")
plt.ylabel("prestige")
plt.show()
Prática 1#
Modelos Lineares#
Exercício: dados Prestige#
Fontes:
https://rstudio-pubs-static.s3.amazonaws.com/63531_55f83f76f1104efd9b6d694f9228d55e.html
Estatística para Ciência de Dados. Notas de aula da Profa. Mariana Cúri.
O arquivo Prestige.csv contém 102 observações e 7 variáveis. A descrição das variáveis no conjunto de dados é a seguinte:
occupation: profissão
education: número médio de anos de estudo dos titulares da profissão.
income: A renda média dos ocupantes ocupacionais, em dólares.
women: a porcentagem de mulheres na ocupação.
prestige: a classificação média de prestígio para a ocupação.
census: o código da ocupação utilizado na pesquisa.
type: profissional e gerencial (prof), colarinho branco (wc), colarinho azul (bc) ou ausente (NA) (Fox e Weisberg 2011)
Ajuste um modelo de regressão linear múltiplo para compreender a associação entre o prestígio e as preditoras renda, educação e mulheres. Verifique se o modelo é adequado.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
import pandas as pd
from scipy import stats
# Dados Prestige - Leitura dos dados a partir de uma pasta local
#pkgdir = '/hdd/MBA/ECD/Data'
#dados = pd.read_csv(f'{pkgdir}/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
dados = pd.read_csv('Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
# Leitura dos dados direto do github
#dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
dados.head()
| occupation | education | income | women | prestige | census | type | |
|---|---|---|---|---|---|---|---|
| 0 | gov.administrators | 13.11 | 12351 | 11.16 | 68.8 | 1113 | prof |
| 1 | general.managers | 12.26 | 25879 | 4.02 | 69.1 | 1130 | prof |
| 2 | accountants | 12.77 | 9271 | 15.70 | 63.4 | 1171 | prof |
| 3 | purchasing.officers | 11.42 | 8865 | 9.11 | 56.8 | 1175 | prof |
| 4 | chemists | 14.62 | 8403 | 11.68 | 73.5 | 2111 | prof |
dados.head(20)
| occupation | education | income | women | prestige | census | type | |
|---|---|---|---|---|---|---|---|
| 0 | gov.administrators | 13.11 | 12351 | 11.16 | 68.8 | 1113 | prof |
| 1 | general.managers | 12.26 | 25879 | 4.02 | 69.1 | 1130 | prof |
| 2 | accountants | 12.77 | 9271 | 15.70 | 63.4 | 1171 | prof |
| 3 | purchasing.officers | 11.42 | 8865 | 9.11 | 56.8 | 1175 | prof |
| 4 | chemists | 14.62 | 8403 | 11.68 | 73.5 | 2111 | prof |
| 5 | physicists | 15.64 | 11030 | 5.13 | 77.6 | 2113 | prof |
| 6 | biologists | 15.09 | 8258 | 25.65 | 72.6 | 2133 | prof |
| 7 | architects | 15.44 | 14163 | 2.69 | 78.1 | 2141 | prof |
| 8 | civil.engineers | 14.52 | 11377 | 1.03 | 73.1 | 2143 | prof |
| 9 | mining.engineers | 14.64 | 11023 | 0.94 | 68.8 | 2153 | prof |
| 10 | surveyors | 12.39 | 5902 | 1.91 | 62.0 | 2161 | prof |
| 11 | draughtsmen | 12.30 | 7059 | 7.83 | 60.0 | 2163 | prof |
| 12 | computer.programers | 13.83 | 8425 | 15.33 | 53.8 | 2183 | prof |
| 13 | economists | 14.44 | 8049 | 57.31 | 62.2 | 2311 | prof |
| 14 | psychologists | 14.36 | 7405 | 48.28 | 74.9 | 2315 | prof |
| 15 | social.workers | 14.21 | 6336 | 54.77 | 55.1 | 2331 | prof |
| 16 | lawyers | 15.77 | 19263 | 5.13 | 82.3 | 2343 | prof |
| 17 | librarians | 14.15 | 6112 | 77.10 | 58.1 | 2351 | prof |
| 18 | vocational.counsellors | 15.22 | 9593 | 34.89 | 58.3 | 2391 | prof |
| 19 | ministers | 14.50 | 4686 | 4.14 | 72.8 | 2511 | prof |
dados.isnull().sum()
occupation 0
education 0
income 0
women 0
prestige 0
census 0
type 4
dtype: int64
dados['type'].fillna(dados['type'].mode()[0],inplace=True)
dados.drop(['occupation','census'],axis=1,inplace=True)
dados.head()
| education | income | women | prestige | type | |
|---|---|---|---|---|---|
| 0 | 13.11 | 12351 | 11.16 | 68.8 | prof |
| 1 | 12.26 | 25879 | 4.02 | 69.1 | prof |
| 2 | 12.77 | 9271 | 15.70 | 63.4 | prof |
| 3 | 11.42 | 8865 | 9.11 | 56.8 | prof |
| 4 | 14.62 | 8403 | 11.68 | 73.5 | prof |
Divisão em treinamento e teste#
from sklearn.model_selection import train_test_split
train, test = train_test_split(dados, test_size=0.2, random_state=0, stratify=dados[['type']])
train.describe()
| education | income | women | prestige | |
|---|---|---|---|---|
| count | 81.000000 | 81.000000 | 81.000000 | 81.000000 |
| mean | 10.774321 | 6928.320988 | 27.667160 | 47.171605 |
| std | 2.741272 | 3989.932120 | 30.426826 | 16.696850 |
| min | 6.380000 | 918.000000 | 0.000000 | 14.800000 |
| 25% | 8.490000 | 4330.000000 | 3.160000 | 35.700000 |
| 50% | 10.640000 | 6259.000000 | 13.620000 | 43.700000 |
| 75% | 12.770000 | 8206.000000 | 48.280000 | 59.600000 |
| max | 15.940000 | 25879.000000 | 97.510000 | 82.300000 |
train.prestige.hist()
<AxesSubplot:>
# Constrói os gráficos de dispersão
plt.scatter(x=train.education,y=train.prestige)
plt.xlabel("anos de educação")
plt.ylabel("prestígio da ocupação")
plt.show()
import seaborn as sns
sns.pairplot(train, kind='reg')
plt.show()
# Outra maneira, diferenciando por "type"
sns.pairplot(train, hue='type', kind='reg')
plt.show()
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(train.drop(['type'],axis=1).corr(),annot=True, square=True, cmap="BuGn")
plt.title('Matriz de correlações de Pearson',fontsize=20)
plt.show()
from statsmodels.formula.api import ols
#Ajusta o modelo de regressão linear múltipla com Prestige como variável resposta
mod = ols('prestige ~ income + education + women',data=train)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.783
Model: OLS Adj. R-squared: 0.775
Method: Least Squares F-statistic: 92.73
Date: Wed, 17 Jan 2024 Prob (F-statistic): 1.72e-25
Time: 02:01:13 Log-Likelihood: -280.54
No. Observations: 81 AIC: 569.1
Df Residuals: 77 BIC: 578.7
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -5.9608 3.633 -1.641 0.105 -13.195 1.273
income 0.0013 0.000 3.843 0.000 0.001 0.002
education 4.1283 0.433 9.535 0.000 3.266 4.990
women -0.0076 0.037 -0.208 0.836 -0.080 0.065
==============================================================================
Omnibus: 0.354 Durbin-Watson: 2.114
Prob(Omnibus): 0.838 Jarque-Bera (JB): 0.378
Skew: -0.151 Prob(JB): 0.828
Kurtosis: 2.855 Cond. No. 3.31e+04
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 3.31e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
mod = ols('prestige ~ income + education',data=train)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.783
Model: OLS Adj. R-squared: 0.778
Method: Least Squares F-statistic: 140.8
Date: Wed, 17 Jan 2024 Prob (F-statistic): 1.30e-26
Time: 02:01:13 Log-Likelihood: -280.57
No. Observations: 81 AIC: 567.1
Df Residuals: 78 BIC: 574.3
Df Model: 2
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -6.0249 3.598 -1.675 0.098 -13.187 1.137
income 0.0013 0.000 5.000 0.000 0.001 0.002
education 4.0879 0.385 10.630 0.000 3.322 4.853
==============================================================================
Omnibus: 0.342 Durbin-Watson: 2.111
Prob(Omnibus): 0.843 Jarque-Bera (JB): 0.412
Skew: -0.148 Prob(JB): 0.814
Kurtosis: 2.813 Cond. No. 3.30e+04
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 3.3e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
Análise de resíduos#
# Estimativa da variância do erro (sigma2), que é o MSE=SQE/(n-p)
res.mse_resid
62.01986979474898
# valores preditos de E(Y)
ypred=res.fittedvalues
# resíduo=observado-ajustado
residuo = res.resid
# objeto para a análise de pontos influentes
infl = res.get_influence()
# diagonal da matriz hat
hii = infl.hat_matrix_diag
# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal
# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external
# DFFITS
(dffits,p) = infl.dffits
# Distância de Cook
(cook,p) = infl.cooks_distance
Elabora os gráficos de todos os resíduos
fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')
for ax in fig.get_axes():
ax.label_outer()
fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(train.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(train.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(train.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')
for ax in fig.get_axes():
ax.label_outer()
Identificando a observações que se destacam das demais em alguns gráficos de diagnóstico
# Instale a plotly se necessário
#!pip install plotly
import plotly.express as px
fig = px.scatter(x = train.index, y=cook)
fig.show()
import plotly.express as px
fig = px.scatter(x = train.index, y=residuo)
fig.show()
fig = px.scatter(x = train.index, y=ypred)
fig.show()
# x and y given as DataFrame columns
import plotly.express as px
fig = px.scatter(x = train.index, y=hii)
fig.show()
Identificada a observação 1. Verifique o que elas tem de especial.
train.iloc[1,]
education 12.27
income 14032
women 0.58
prestige 66.1
type prof
Name: 95, dtype: object
train.describe()
| education | income | women | prestige | |
|---|---|---|---|---|
| count | 81.000000 | 81.000000 | 81.000000 | 81.000000 |
| mean | 10.774321 | 6928.320988 | 27.667160 | 47.171605 |
| std | 2.741272 | 3989.932120 | 30.426826 | 16.696850 |
| min | 6.380000 | 918.000000 | 0.000000 | 14.800000 |
| 25% | 8.490000 | 4330.000000 | 3.160000 | 35.700000 |
| 50% | 10.640000 | 6259.000000 | 13.620000 | 43.700000 |
| 75% | 12.770000 | 8206.000000 | 48.280000 | 59.600000 |
| max | 15.940000 | 25879.000000 | 97.510000 | 82.300000 |
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
from scipy import stats
stats.shapiro(residuo)
ShapiroResult(statistic=0.9904203414916992, pvalue=0.8151065707206726)
Teste do modelo#
y_pred = res.predict(test)
plt.figure(figsize=(6,6))
plt.scatter(test.prestige,y_pred)
xi = np.min([test.prestige,y_pred])
xf = np.max([test.prestige,y_pred])
xlim = [xi-5,xf+5]
plt.xlim(xlim)
plt.ylim(xlim)
abline_values = [1 * i + 0 for i in xlim]
plt.plot(xlim, abline_values, 'gray', ls='--')
plt.xlabel('Income')
plt.ylabel('Prediction')
plt.show()
from sklearn.metrics import r2_score, mean_squared_error
rmse = mean_squared_error(test.prestige, y_pred,squared=False)
r2 = r2_score(test.prestige,y_pred)
print('r2 = ', np.round(r2,2))
print('RMSE = ', np.round(rmse,2))
r2 = 0.84
RMSE = 7.59
Transformação da variável ‘income’#
# Os autores em https://rstudio-pubs-static.s3.amazonaws.com/63531_55f83f76f1104efd9b6d694f9228d55e.html
# sugerem a transformação logaritmica da variável income e verificamos se type deve ser adicionada
# Modelo de regressão com a variável "income" transformada
train_l_income = np.log(train.income) # base e
test_l_income = np.log(test.income)
train_l = train.copy()
test_l = test.copy()
train_l['l_income'] = train_l_income
test_l['l_income'] = test_l_income
mod = ols('prestige ~ l_income + education + women',data=train_l)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.822
Model: OLS Adj. R-squared: 0.815
Method: Least Squares F-statistic: 118.5
Date: Wed, 17 Jan 2024 Prob (F-statistic): 8.89e-29
Time: 02:01:16 Log-Likelihood: -272.56
No. Observations: 81 AIC: 553.1
Df Residuals: 77 BIC: 562.7
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -113.0827 18.352 -6.162 0.000 -149.627 -76.539
l_income 13.7891 2.339 5.896 0.000 9.132 18.446
education 3.6299 0.400 9.073 0.000 2.833 4.427
women 0.0407 0.035 1.170 0.245 -0.029 0.110
==============================================================================
Omnibus: 1.053 Durbin-Watson: 1.971
Prob(Omnibus): 0.591 Jarque-Bera (JB): 0.592
Skew: 0.182 Prob(JB): 0.744
Kurtosis: 3.207 Cond. No. 976.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Modelo de regressão com a variável "income" transformada e variável type
mod = ols('prestige ~ l_income+education+women+type',data=train_l)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.856
Model: OLS Adj. R-squared: 0.847
Method: Least Squares F-statistic: 89.47
Date: Wed, 17 Jan 2024 Prob (F-statistic): 3.57e-30
Time: 02:01:16 Log-Likelihood: -263.86
No. Observations: 81 AIC: 539.7
Df Residuals: 75 BIC: 554.1
Df Model: 5
Covariance Type: nonrobust
================================================================================
coef std err t P>|t| [0.025 0.975]
--------------------------------------------------------------------------------
Intercept -95.6422 18.509 -5.167 0.000 -132.514 -58.771
type[T.prof] 6.3789 4.092 1.559 0.123 -1.772 14.530
type[T.wc] -4.3895 2.731 -1.607 0.112 -9.830 1.051
l_income 12.5355 2.173 5.770 0.000 8.208 16.863
education 2.8905 0.646 4.476 0.000 1.604 4.177
women 0.0568 0.034 1.650 0.103 -0.012 0.125
==============================================================================
Omnibus: 0.069 Durbin-Watson: 2.021
Prob(Omnibus): 0.966 Jarque-Bera (JB): 0.208
Skew: 0.056 Prob(JB): 0.901
Kurtosis: 2.779 Cond. No. 1.09e+03
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.09e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
Modelo de regressão final?
Vamos exlcuir type com base no alto valor p, mas ela pode ser incluída em interação com income, veja a próxima célula
Que outros critérios você utilizaria para a seleção de variáveis?
mod = ols('prestige ~ l_income+education',data=train_l)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.819
Model: OLS Adj. R-squared: 0.814
Method: Least Squares F-statistic: 176.3
Date: Wed, 17 Jan 2024 Prob (F-statistic): 1.16e-29
Time: 02:01:16 Log-Likelihood: -273.28
No. Observations: 81 AIC: 552.6
Df Residuals: 78 BIC: 559.7
Df Model: 2
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -98.9631 13.862 -7.139 0.000 -126.561 -71.365
l_income 12.0142 1.785 6.732 0.000 8.461 15.567
education 3.8578 0.350 11.011 0.000 3.160 4.555
==============================================================================
Omnibus: 0.879 Durbin-Watson: 2.008
Prob(Omnibus): 0.644 Jarque-Bera (JB): 0.529
Skew: 0.190 Prob(JB): 0.768
Kurtosis: 3.114 Cond. No. 246.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Análise extra: inclusão de interação de variáveis
Modelo de regressão com interação
\(prestige = \beta_0 + \beta_1*l\_income + \beta_2*education + \beta_3*type + \beta_4*type*l\_income + \beta_5*type*education\)
mod = ols('prestige ~ type*(l_income)+type*(education)',data=train_l)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.865
Model: OLS Adj. R-squared: 0.850
Method: Least Squares F-statistic: 57.61
Date: Wed, 17 Jan 2024 Prob (F-statistic): 3.03e-28
Time: 02:01:16 Log-Likelihood: -261.40
No. Observations: 81 AIC: 540.8
Df Residuals: 72 BIC: 562.3
Df Model: 8
Covariance Type: nonrobust
==========================================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------------------
Intercept -107.2086 19.057 -5.626 0.000 -145.198 -69.219
type[T.prof] 92.1316 34.235 2.691 0.009 23.885 160.378
type[T.wc] 30.5116 39.957 0.764 0.448 -49.141 110.164
l_income 14.4362 2.406 6.001 0.000 9.640 19.232
type[T.prof]:l_income -9.4205 3.835 -2.456 0.016 -17.066 -1.775
type[T.wc]:l_income -6.2796 4.533 -1.385 0.170 -15.317 2.758
education 2.4575 0.874 2.811 0.006 0.715 4.200
type[T.prof]:education 0.1608 1.399 0.115 0.909 -2.627 2.949
type[T.wc]:education 1.9388 2.105 0.921 0.360 -2.258 6.136
==============================================================================
Omnibus: 0.165 Durbin-Watson: 1.979
Prob(Omnibus): 0.921 Jarque-Bera (JB): 0.003
Skew: -0.005 Prob(JB): 0.998
Kurtosis: 3.028 Cond. No. 986.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
test_l
| education | income | women | prestige | type | l_income | |
|---|---|---|---|---|---|---|
| 42 | 9.22 | 5511 | 7.62 | 36.1 | wc | 8.614501 |
| 3 | 11.42 | 8865 | 9.11 | 56.8 | prof | 9.089866 |
| 72 | 7.42 | 1890 | 72.24 | 23.2 | bc | 7.544332 |
| 20 | 15.97 | 12480 | 19.59 | 84.6 | prof | 9.431883 |
| 44 | 10.51 | 3161 | 96.14 | 38.1 | wc | 8.058644 |
| 74 | 6.74 | 3485 | 39.48 | 28.8 | bc | 8.156223 |
| 30 | 12.79 | 5180 | 76.04 | 67.5 | wc | 8.552560 |
| 18 | 15.22 | 9593 | 34.89 | 58.3 | prof | 9.168789 |
| 23 | 15.96 | 25308 | 10.56 | 87.2 | prof | 10.138876 |
| 35 | 11.49 | 3148 | 95.97 | 41.9 | wc | 8.054523 |
| 53 | 9.93 | 2370 | 3.69 | 23.3 | bc | 7.770645 |
| 92 | 7.81 | 4549 | 2.46 | 29.9 | bc | 8.422663 |
| 99 | 8.37 | 4753 | 0.00 | 26.1 | bc | 8.466531 |
| 98 | 7.93 | 4224 | 3.59 | 25.1 | bc | 8.348538 |
| 10 | 12.39 | 5902 | 1.91 | 62.0 | prof | 8.683047 |
| 11 | 12.30 | 7059 | 7.83 | 60.0 | prof | 8.862059 |
| 62 | 9.46 | 611 | 96.53 | 25.9 | bc | 6.415097 |
| 76 | 8.81 | 6686 | 4.28 | 44.2 | bc | 8.807771 |
| 27 | 9.45 | 3485 | 76.14 | 34.9 | bc | 8.156223 |
| 89 | 8.24 | 8880 | 0.65 | 51.1 | bc | 9.091557 |
| 46 | 11.13 | 5052 | 56.10 | 51.1 | wc | 8.527539 |
Teste do modelo#
y_pred = res.predict(test_l)
plt.figure(figsize=(6,6))
plt.scatter(test_l.prestige,y_pred)
xi = np.min([test_l.prestige,y_pred])
xf = np.max([test_l.prestige,y_pred])
xlim = [xi-5,xf+5]
plt.xlim(xlim)
plt.ylim(xlim)
abline_values = [1 * i + 0 for i in xlim]
plt.plot(xlim, abline_values, 'gray', ls='--')
plt.xlabel('Income')
plt.ylabel('Prediction')
plt.show()
from sklearn.metrics import r2_score, mean_squared_error
rmse = mean_squared_error(test.prestige, y_pred,squared=False)
r2 = r2_score(test.prestige,y_pred)
print('r2 = ', np.round(r2,2))
print('RMSE = ', np.round(rmse,2))
r2 = 0.82
RMSE = 8.1
Seleção de atributos com stepwise#
Transformação logaritimica do income
Transformando a variável type em valores numéricos (get_dummy)
train_l = pd.get_dummies(train_l, columns=['type'])
test_l = pd.get_dummies(test_l, columns=['type'])
train_l = train_l.astype({'type_bc': int, 'type_wc': int, 'type_prof': int})
test_l = test_l.astype({'type_bc': int, 'type_wc': int, 'type_prof': int})
train_l.head()
| education | income | women | prestige | l_income | type_bc | type_prof | type_wc | |
|---|---|---|---|---|---|---|---|---|
| 14 | 14.36 | 7405 | 48.28 | 74.9 | 8.909911 | 0 | 1 | 0 |
| 95 | 12.27 | 14032 | 0.58 | 66.1 | 9.549096 | 0 | 1 | 0 |
| 52 | 9.62 | 918 | 7.00 | 14.8 | 6.822197 | 1 | 0 | 0 |
| 93 | 8.33 | 6928 | 0.61 | 42.9 | 8.843326 | 1 | 0 | 0 |
| 49 | 9.84 | 7482 | 17.04 | 41.5 | 8.920255 | 0 | 0 | 1 |
import statsmodels.api as sm
def backward_elimination(data, target, significance_level = 0.05):
features = data.columns.tolist()
while(len(features)>0):
features_with_constant = sm.add_constant(data[features])
p_values = sm.OLS(target, features_with_constant).fit().pvalues[1:] # we exclude the intercept
max_p_value = p_values.max()
if max_p_value >= significance_level:
excluded_feature = p_values.idxmax()
features.remove(excluded_feature)
else:
break
return features
# Use the function on your data
selected_features = backward_elimination(train_l[['education', 'l_income', 'women','type_bc','type_prof','type_wc']],
train_l['prestige'],significance_level = 0.02)
print('The selected features are:', selected_features)
The selected features are: ['education', 'l_income', 'type_wc']
#mod = ols('prestige ~ education + l_income + women + type_bc + type_prof + type_prof + type_wc',data=train_l)
mod = ols('prestige ~ education + l_income + type_wc',data=train_l)
res = mod.fit()
print(res.summary())
OLS Regression Results
==============================================================================
Dep. Variable: prestige R-squared: 0.842
Model: OLS Adj. R-squared: 0.836
Method: Least Squares F-statistic: 136.9
Date: Wed, 17 Jan 2024 Prob (F-statistic): 8.85e-31
Time: 02:01:17 Log-Likelihood: -267.70
No. Observations: 81 AIC: 543.4
Df Residuals: 77 BIC: 553.0
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -84.8363 13.682 -6.201 0.000 -112.080 -57.592
education 4.0923 0.336 12.164 0.000 3.422 4.762
l_income 10.2639 1.755 5.847 0.000 6.769 13.759
type_wc -6.3851 1.894 -3.371 0.001 -10.157 -2.613
==============================================================================
Omnibus: 0.244 Durbin-Watson: 2.073
Prob(Omnibus): 0.885 Jarque-Bera (JB): 0.405
Skew: 0.101 Prob(JB): 0.817
Kurtosis: 2.719 Cond. No. 259.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Teste do modelo#
y_pred = res.predict(test_l)
plt.figure(figsize=(6,6))
plt.scatter(test_l.prestige,y_pred)
xi = np.min([test_l.prestige,y_pred])
xf = np.max([test_l.prestige,y_pred])
xlim = [xi-5,xf+5]
plt.xlim(xlim)
plt.ylim(xlim)
abline_values = [1 * i + 0 for i in xlim]
plt.plot(xlim, abline_values, 'gray', ls='--')
plt.xlabel('Income')
plt.ylabel('Prediction')
plt.show()
from sklearn.metrics import r2_score, mean_squared_error
rmse = mean_squared_error(test_l.prestige, y_pred,squared=False)
r2 = r2_score(test.prestige,y_pred)
print('r2 = ', np.round(r2,2))
print('RMSE = ', np.round(rmse,2))
r2 = 0.84
RMSE = 7.65
Análise de resíduos#
# Estimativa da variância do erro (sigma2), que é o MSE=SQE/(n-p)
res.mse_resid
45.72493077668226
# valores preditos de E(Y)
ypred=res.fittedvalues
# resíduo=observado-ajustado
residuo = res.resid
# objeto para a análise de pontos influentes
infl = res.get_influence()
# diagonal da matriz hat
hii = infl.hat_matrix_diag
# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal
# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external
# DFFITS
(dffits,p) = infl.dffits
# Distância de Cook
(cook,p) = infl.cooks_distance
Elabora os gráficos de todos os resíduos
fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')
for ax in fig.get_axes():
ax.label_outer()
fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(train.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(train.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(train.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')
for ax in fig.get_axes():
ax.label_outer()
Identificando a observações que se destacam das demais em alguns gráficos de diagnóstico
# Instale a plotly se necessário
#!pip install plotly
import plotly.express as px
fig = px.scatter(x = train.index, y=cook)
fig.show()
# x and y given as DataFrame columns
import plotly.express as px
fig = px.scatter(x = train.index, y=hii)
fig.show()
Identificada a observação 1. Verifique o que elas tem de especial.
train.iloc[52,]
education 11.09
income 6992
women 24.44
prestige 47.1
type wc
Name: 55, dtype: object
train.describe()
| education | income | women | prestige | |
|---|---|---|---|---|
| count | 81.000000 | 81.000000 | 81.000000 | 81.000000 |
| mean | 10.774321 | 6928.320988 | 27.667160 | 47.171605 |
| std | 2.741272 | 3989.932120 | 30.426826 | 16.696850 |
| min | 6.380000 | 918.000000 | 0.000000 | 14.800000 |
| 25% | 8.490000 | 4330.000000 | 3.160000 | 35.700000 |
| 50% | 10.640000 | 6259.000000 | 13.620000 | 43.700000 |
| 75% | 12.770000 | 8206.000000 | 48.280000 | 59.600000 |
| max | 15.940000 | 25879.000000 | 97.510000 | 82.300000 |
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
from scipy import stats
stats.shapiro(residuo)
ShapiroResult(statistic=0.9898176789283752, pvalue=0.7772987484931946)
Aula 8 - Modelos Lineares Generalizados#
Programa#
Modelos lineares generalizados.
Família exponencial de distribuições.
O modelo de regressão logística
A qualidade do ajuste.
Aplicações (enfoque frequentista e enfoque Bayesiano)
Referências e Leituras sugeridas:
Paula, G. A. Modelos de regressão: com apoio computacional. São Paulo: IME-USP, 2013. Disponível em https://www.ime.usp.br/~giapaula/texto_2013.pdf
Dobson, A. J.; Barnett, Adrian G. (2018). An introduction to generalized linear models. CRC press.
Modelos lineares#
Objetivos
Predizer \(Y\) a partir do conhecimento de variáveis preditoras em \(X = x\).
Um modelo linear considerando pares observados de \((X_i, Y_i), i=1,\ldots,n\), em que \(X_i\) é um vetor de preditoras é dado por
em que, para o \(i\)-ésimo elemento amostral, temos
\(Y_i\) é a variável resposta (aleatória observável),
\(X_i\) contém variáveis preditoras (vetor conhecido, ou seja, não-aleatório),
\(\beta\) é um vetor de parâmetros de interesse, que queremos estimar,
\(\epsilon_i\) é o erro aleatório (não observável).
Suposições do modelo linear geral:
\( E(Y_i) = \mu_i = X_i^\top\beta\)
\(\epsilon_i \stackrel{i.i.d}{\sim} N(0, \sigma^2)\)
Consequentemente \( Y_i|X_i \sim N(\mu_i, \sigma^2).\)
O modelo linear (normal) é muito útil, mas nem sempre as suposições estão satisfeitas. É importante ter metodologias mais adequadas, em especial, para casos em que
A variável resposta não possui distribuição Normal.
A associação entre a resposta e as preditoras não é linear.
Modelo linear generalizado (MLG)#
Nelder, John; Wedderburn, Robert (1972). Generalized Linear Models. Blackwell Publishing. Journal of the Royal Statistical Society. Series A (General). 135: 370–384. JSTOR 2344614. doi:10.2307/2344614
Os MLGs propõem a modelagem para variáveis na família exponencial de distribuições, que inclui
Binomial (Bernoulli)
Poisson
Normal
Binomial Negativa
Gama
Gaussiana Inversa
outras
A família exponencial de distribuições#
Seja \(Y_i\) uma variável aleatória cuja distribuição pode ser escrita na forma
Dizemos que a distribuição de \(Y_i\) pertence à família exponencial e escrevemos $\(\large Y_i \sim FE(\mu_i, \phi),\)$
em que \(\mu_i = E(Y_i) = b'(\theta_i)\) é o parâmetro de posição e \(\phi^{-1}\) o parâmetro de dispersão.
Além disso, \(Var(Y_i) = \phi^{-1}V_i\) com \(V_i = \displaystyle\frac{d\mu}{d\theta}\) a função de variância.
Então dizemos que um MLG é determinado pela função de ligação \(g(\mu_i) = \eta_i\) e pela função de variância \(V(\mu)\).
Valores de \(b\), \(\theta\), \(\phi\) e \(V(\mu)\) para algumas distribuições são apresentados na tabela a seguir
| Distribuição | $b$ | |||
|---|---|---|---|---|
| Normal | \begin{eqnarray}\theta^2/2\end{eqnarray} | \begin{eqnarray}\mu\end{eqnarray} | \begin{eqnarray}\sigma^{-2}\end{eqnarray} | \begin{eqnarray}1\end{eqnarray} |
| Poisson | \begin{eqnarray}e^{\theta}\end{eqnarray} | \begin{eqnarray}\log \mu\end{eqnarray} | \begin{eqnarray}1\end{eqnarray} | \begin{eqnarray}\mu\end{eqnarray} |
| Binomial | \begin{eqnarray}\log\left(1+e^\theta\right)\end{eqnarray} | \begin{eqnarray} \log\{\mu/(1-\mu)\}\end{eqnarray} | \begin{eqnarray} n\end{eqnarray} | \begin{eqnarray} \mu(1-\mu)\end{eqnarray} |
Função de ligação#
O modelo linear generalizado é definido por
em que
\( \eta_i = X_i^\top\beta\) é o preditor linear,
\(\beta = (\beta_0, \beta_1,\ldots, \beta_p)^\top\), \(p<n\) é um vetor de parâmetros desconhecidos (coeficientes da regressão),
\( X_i = (1, X_{i1},\ldots, X_{ip})^\top\) representa os valores de \(p\) variáveis preditoras e
\(g(\mu_i)\) é a função de ligação, uma função monótona e diferenciável.
Exemplos#
Modelo Normal#
Se \(Y\sim N(\mu, \sigma^2)\), com densidade
em que \(-\infty < \mu < \infty\) e \(\sigma^2>0\).
Logo, \(\theta = \mu\), \(b(\theta) = \theta^2/2 \), \(\phi=\sigma^{-2}\) e \(c(y, \phi) = \frac{1}{2} \log(\phi/2\pi) - \phi y^2/2\). Verifica-se que \(V(\mu) = 1\).
Modelo Poisson: para dados de contagem#
Se \(Y\sim P(\mu)\), com densidade dada por
em que \(\mu>0\) e \(y=0,1,...\)
Assim \(\theta = \log\mu\), \(b(\theta) = e^\theta\), \(\phi=1\) e \(c(y, \phi) = -\log y!\). Segue que \(V(\mu) = \mu\).
Modelo Binomial: para modelar proporções#
Se \(Y^\star\) é a proporção de sucessos em \(n\) ensaios independentes de Bernoulli, cada um com probabilidade de sucesso \(\mu\), então \(nY^\star\sim B(n,\mu)\), com densidade dada por
em que \(\mu>0\), \(y^\star<1\). Nesse caso, \(\phi =n\), \(\theta = \log\displaystyle\left(\frac{\mu}{1-\mu}\right)\), \(b(\theta) = \log(1+e^\theta)\), e \(c(y^\star, \phi) = \log \left(\begin{array}{c} \phi\\ \phi y^\star\end{array}\right)\). Segue que \(V(\mu) = \mu(1-\mu)\).
Função de ligação canônica#
Se \(\theta_i = \eta_i = X_i^\top\beta\), então \(\eta\) é chamada de ligação canônica.
No modelo normal, a ligação canônica é \(\eta = \mu \)
No modelo Poisson, a ligação canônica é \(\eta = \log\mu\)
No modelo binomial, a ligação canônica é \(\eta = \log\displaystyle\left\{\frac{\mu}{1-\mu}\right\}\)
O modelo de regressão logística#
Um dos modelos lineares mais utilizados é o modelo de regressão logística.
Neste modelo, consideramos
\(Y_i = \left\{\begin{array}{l}1, \mbox{ se o indivíduo i possui determinada característica}\\ 0, \mbox{ caso contrário}\end{array}\right.\)
Supondo que \(P(Y_i|X_i) = \pi(X_i)\) e que
e queremos estimar os parâmetros \(\alpha\) e \(\beta\) para compreender como \(X_i\) e \(Y_i\) estão associados.
Suponha que \(X_i=1\) indique que o indivíduo \(i\) possui um fator de risco para uma determinada doença (indicada por \(Y_i=1)\) e foram observados \(n_1\) indivíduos com a presença deste fator, e \(n_2\) indivíduos sem a presença deste fator (\(X_i=0\)).
Para os indivíduos que possuiam o fator, a chance de desenvolvimento da doença fica
enquanto que a chance de desenvolvimento da doença no indivíduo com ausência do fator é
A razão de chances nesse caso fica
Regressão logística múltipla#
O modelo de regressão logística pode ser estendido para incluir \(p\) variáveis preditoras:
\(Y_i = \left\{\begin{array}{l}1, \mbox{ se o indivíduo i possui determinada característica}\\ 0, \mbox{ caso contrário}\end{array}\right.\)
Supondo que \(P(Y_i|X_i) = \pi(X_i)\) e que
A estimação dos parâmetros#
No enfoque clássico, é comum obter os estimadores de máxima verossimilhança por métodos iterativos como
Método de Newton-Raphson
Método escore de Fisher
Método dos mínimos quadrados ou mínimos quadrados reponderados
No enfoque Bayesiano, supomos distribuições de probabilidade para os coeficientes da regressão com métodos computacionais e obtemos estimativas a partir de amostras da distribuição a posteriori dos parâmetros, como Monte Carlo Hamiltoniano ou No U-Turn Sampler (NUTS).
A qualidade do ajuste#
A qualidade do ajuste pode ser avaliada com
Análise de diagnóstico
Resíduos ordinários,
Resíduos studentizados,
Pontos de alavanca,
Distância de Cook
Envelopes para os resíduos (disponível em R por https://www.ime.usp.br/~giapaula)
Deviance (função desvio, para mais informações ver https://www.ime.usp.br/~giapaula/texto_2013.pdf)
Resíduo componente do desvio
Métricas de ajuste
Erro absoluto médio
Erro quadrático médio
Raiz do erro quadrático médio
Seleção de modelos
Método forward
Método backward
Método stepwise
Critérios de informação (AIC, BIC)
Modelos Lineares generalizados#
import numpy as np
import statsmodels.api as sm
from scipy import stats
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
import seaborn as sns
%matplotlib inline
plt.rc("figure", figsize=(16,8))
plt.rc("font", size=14)
MLG: Dados com resposta binomial#
Referência sugerida: https://www.statsmodels.org/devel/examples/notebooks/generated/glm.html
Aplicação#
Considere os dados do arquivo dados_banco.csv. Estão disponíveis as variáveis:
Cliente: Identificador do cliente.
Sexo: Feminino (F) ou Masculino (M)
Idade: Idade do cliente, em anos completos.
Empresa: Tipo da empresa em que trabalha: Pública, Privada ou Autônomo
Salário: Salário declarado pelo cliente na abertura da conta, em reais.
Saldo_cc: Saldo em conta corrente, em reais.
Saldo_poupança: Saldo em poupança, em reais.
Saldo_investimento: Saldo em investimentos, em reais.
Devedor_cartao: Valor em atraso no cartão de crédito, em reais.
Inadimplente: Se o cliente é considerado inadimplente atualmente (1) ou não (0), de acordo com critérios preestabelecidos.
import pandas as pd
# Dados banco - Leitura dos dados
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
# Vamos trabalhar com uma amostra como na Aula 6
dados = dados.sample(n=500, replace=False, random_state=10)
dados.head()
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | |
|---|---|---|---|---|---|---|---|---|---|
| Cliente | |||||||||
| 53080 | M | 31 | Privada | 5717.00 | 1205.56 | 0.0 | 0.0 | 2313.15 | 0 |
| 86540 | M | 38 | Privada | 6523.00 | 1370.21 | 0.0 | 0.0 | 3202.99 | 0 |
| 96211 | M | 33 | Pública | 5378.00 | 750.60 | 0.0 | 0.0 | 4225.13 | 0 |
| 42117 | M | 34 | Autônomo | 5496.82 | 896.48 | 0.0 | 0.0 | 3365.48 | 1 |
| 49964 | M | 35 | Privada | 6137.00 | 774.12 | 0.0 | 0.0 | 4135.15 | 0 |
Divisão da base em treino e teste
dados_treino, dados_teste = train_test_split(dados,train_size = 0.8,random_state=3)
Ajustando um MLG com resposta binária#
### Ajustando um MLG com resposta binária, iniciando com 4 preditoras
preditoras = dados_treino[['Idade','Devedor_cartao','Salario','Saldo_cc']]
resposta = dados_treino[['Inadimplente']]
preditoras
| Idade | Devedor_cartao | Salario | Saldo_cc | |
|---|---|---|---|---|
| Cliente | ||||
| 56455 | 35 | 2370.58 | 5645.78 | 881.15 |
| 85723 | 31 | 2509.16 | 5560.00 | 411.92 |
| 3478 | 28 | 4269.03 | 5234.00 | 982.51 |
| 31829 | 34 | 1196.30 | 5610.00 | 1084.03 |
| 38218 | 29 | 2191.62 | 5131.00 | 674.81 |
| ... | ... | ... | ... | ... |
| 45916 | 30 | 5102.12 | 4957.82 | 770.16 |
| 36647 | 31 | 2012.71 | 5111.68 | 591.95 |
| 2141 | 29 | 3895.29 | 5218.00 | 1141.87 |
| 77304 | 36 | 3192.87 | 5602.00 | 1234.37 |
| 75695 | 31 | 4368.80 | 5578.00 | 903.62 |
400 rows × 4 columns
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: Inadimplente No. Observations: 400
Model: GLM Df Residuals: 396
Model Family: Binomial Df Model: 3
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -137.45
Date: Wed, 17 Jan 2024 Deviance: 274.90
Time: 05:10:18 Pearson chi2: 336.
No. Iterations: 6 Pseudo R-squ. (CS): 0.4037
Covariance Type: nonrobust
==================================================================================
coef std err z P>|z| [0.025 0.975]
----------------------------------------------------------------------------------
Idade 0.3067 0.099 3.090 0.002 0.112 0.501
Devedor_cartao 0.0007 9.76e-05 7.384 0.000 0.001 0.001
Salario -0.0015 0.001 -2.609 0.009 -0.003 -0.000
Saldo_cc -0.0069 0.001 -7.334 0.000 -0.009 -0.005
==================================================================================
ajustado = res.predict(preditoras)
X_teste = dados_teste[['Idade','Devedor_cartao','Salario','Saldo_cc']]
Y_teste = dados_teste[['Inadimplente']]
predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.08727882133957354
res.aic
282.90066363214913
Segundo modelo, excluindo Saldo_cc
preditoras = dados_treino[['Idade','Devedor_cartao', 'Salario']]
resposta = dados_treino[['Inadimplente']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: Inadimplente No. Observations: 400
Model: GLM Df Residuals: 397
Model Family: Binomial Df Model: 2
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -180.33
Date: Wed, 17 Jan 2024 Deviance: 360.67
Time: 05:10:18 Pearson chi2: 386.
No. Iterations: 5 Pseudo R-squ. (CS): 0.2611
Covariance Type: nonrobust
==================================================================================
coef std err z P>|z| [0.025 0.975]
----------------------------------------------------------------------------------
Idade 0.2325 0.081 2.883 0.004 0.074 0.391
Devedor_cartao 0.0007 8.5e-05 8.093 0.000 0.001 0.001
Salario -0.0019 0.000 -4.129 0.000 -0.003 -0.001
==================================================================================
ajustado = res.predict()
X_teste = dados_teste[['Idade','Devedor_cartao','Salario']]
Y_teste = dados_teste[['Inadimplente']]
predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.12779719792213348
res.aic
366.6680677035512
n=len(dados_treino)
dados_treino.loc[:,'const'] = np.ones(n).reshape(n,1)
dados_treino.head()
| Sexo | Idade | Empresa | Salario | Saldo_cc | Saldo_poupança | Saldo_investimento | Devedor_cartao | Inadimplente | const | |
|---|---|---|---|---|---|---|---|---|---|---|
| Cliente | ||||||||||
| 56455 | M | 35 | Privada | 5645.78 | 881.15 | 0.0 | 0.0 | 2370.58 | 1 | 1.0 |
| 85723 | F | 31 | Privada | 5560.00 | 411.92 | 0.0 | 0.0 | 2509.16 | 0 | 1.0 |
| 3478 | M | 28 | Pública | 5234.00 | 982.51 | 0.0 | 0.0 | 4269.03 | 0 | 1.0 |
| 31829 | M | 34 | Pública | 5610.00 | 1084.03 | 0.0 | 0.0 | 1196.30 | 0 | 1.0 |
| 38218 | F | 29 | Pública | 5131.00 | 674.81 | 0.0 | 0.0 | 2191.62 | 0 | 1.0 |
preditoras = dados_treino[['Idade','Devedor_cartao', 'Salario']]
resposta = dados_treino[['Inadimplente']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: Inadimplente No. Observations: 400
Model: GLM Df Residuals: 397
Model Family: Binomial Df Model: 2
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -180.33
Date: Wed, 17 Jan 2024 Deviance: 360.67
Time: 05:10:18 Pearson chi2: 386.
No. Iterations: 5 Pseudo R-squ. (CS): 0.2611
Covariance Type: nonrobust
==================================================================================
coef std err z P>|z| [0.025 0.975]
----------------------------------------------------------------------------------
Idade 0.2325 0.081 2.883 0.004 0.074 0.391
Devedor_cartao 0.0007 8.5e-05 8.093 0.000 0.001 0.001
Salario -0.0019 0.000 -4.129 0.000 -0.003 -0.001
==================================================================================
res.aic
366.6680677035512
Análise de diagnóstico para o segundo modelo, agora com a base toda
preditoras = dados[['Idade','Devedor_cartao', 'Salario']]
resposta = dados[['Inadimplente']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
ajustado = res.predict()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: Inadimplente No. Observations: 500
Model: GLM Df Residuals: 497
Model Family: Binomial Df Model: 2
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -219.33
Date: Wed, 17 Jan 2024 Deviance: 438.66
Time: 05:10:18 Pearson chi2: 473.
No. Iterations: 5 Pseudo R-squ. (CS): 0.2481
Covariance Type: nonrobust
==================================================================================
coef std err z P>|z| [0.025 0.975]
----------------------------------------------------------------------------------
Idade 0.2322 0.075 3.079 0.002 0.084 0.380
Devedor_cartao 0.0007 7.74e-05 8.822 0.000 0.001 0.001
Salario -0.0020 0.000 -4.431 0.000 -0.003 -0.001
==================================================================================
# Gráfico de pontos de alavanca
fig, ax = plt.subplots()
plt.plot(res.get_hat_matrix_diag(), '.')
ax.set_title('Pontos de alavanca')
ax.set_ylabel('$h_{ii}$')
ax.set_xlabel('índice das observações');
plt.show()
# Gráfico de Resíduo Componente do desvio
fig, ax = plt.subplots()
plt.plot(ajustado,res.resid_deviance, '.')
ax.set_ylabel('Resíduo componente do desvio')
ax.set_xlabel('valor ajustado');
plt.show()
# x and y given as DataFrame columns
import plotly.express as px
fig = px.scatter(x = ajustado, y=res.resid_deviance)
fig.show()
Modelos lineares generalizados: um enfoque Bayesiano#
!pip install arviz
Requirement already satisfied: arviz in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.23.5)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.11.4)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz) (23.2)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (2023.7.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.3.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz) (0.6.0)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz) (3.9.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (4.47.2)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (1.4.5)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (9.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (3.1.1)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz) (2023.3.post1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz) (1.16.0)
pip install --upgrade numba
Requirement already satisfied: numba in /usr/local/lib/python3.10/dist-packages (0.58.1)
Requirement already satisfied: llvmlite<0.42,>=0.41.0dev0 in /usr/local/lib/python3.10/dist-packages (from numba) (0.41.1)
Requirement already satisfied: numpy<1.27,>=1.22 in /usr/local/lib/python3.10/dist-packages (from numba) (1.23.5)
pip show numba
Name: numba
Version: 0.58.1
Summary: compiling Python code using LLVM
Home-page: https://numba.pydata.org
Author:
Author-email:
License: BSD
Location: /usr/local/lib/python3.10/dist-packages
Requires: llvmlite, numpy
Required-by: librosa
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import patsy as pt
import pymc as pm
import seaborn as sns
print(f"Running on PyMC3 v{pm.__version__}")
Running on PyMC3 v5.7.2
%config InlineBackend.figure_format = 'retina'
RANDOM_SEED = 8927
np.random.seed(RANDOM_SEED)
az.style.use("arviz-darkgrid")
Aplicação#
Este conjunto de dados fictício foi criado para emular alguns dados criados como parte de um estudo do self quantificado, e os dados reais são mais complicados do que isso.
Descrição dos dados
O sujeito espirra N vezes por dia, registrado como nsneeze (int)
O sujeito pode ou não beber álcool durante o dia, registrado como álcool (booleano)
O sujeito pode ou não tomar um medicamento anti-histamínico durante esse dia, registrado como ação negativa denominada (booleano)
Os dados são agregados por dia, para produzir uma contagem total de espirros naquele dia, com um sinalizador booleano para o uso de álcool e anti-histamínicos, com a grande suposição de que os espirros têm uma relação causal direta.
Crie 4000 dias de dados: contagens diárias de espirros que são distribuídos por Poisson durante o consumo de álcool e o uso de anti-histamínicos
# decide poisson theta values
theta_noalcohol_meds = 1 # no alcohol, took an antihist
theta_alcohol_meds = 3 # alcohol, took an antihist
theta_noalcohol_antihist = 6 # no alcohol, no antihist
theta_alcohol_antihist = 36 # alcohol, no antihist
# create samples
q = 1000
df = pd.DataFrame(
{
"nsneeze": np.concatenate(
(
np.random.poisson(theta_noalcohol_meds, q),
np.random.poisson(theta_alcohol_meds, q),
np.random.poisson(theta_noalcohol_antihist, q),
np.random.poisson(theta_alcohol_antihist, q),
)
),
"alcohol": np.concatenate(
(
np.repeat(False, q),
np.repeat(True, q),
np.repeat(False, q),
np.repeat(True, q),
)
),
"antihist": np.concatenate(
(
np.repeat(False, q),
np.repeat(False, q),
np.repeat(True, q),
np.repeat(True, q),
)
),
}
)
df.tail()
| nsneeze | alcohol | antihist | |
|---|---|---|---|
| 3995 | 40 | True | True |
| 3996 | 30 | True | True |
| 3997 | 37 | True | True |
| 3998 | 22 | True | True |
| 3999 | 33 | True | True |
df.groupby(["alcohol", "antihist"]).mean().unstack()
| nsneeze | ||
|---|---|---|
| antihist | False | True |
| alcohol | ||
| False | 1.047 | 6.002 |
| True | 3.089 | 36.004 |
g = sns.catplot(
x="nsneeze",
row="antihist",
col="alcohol",
data=df,
kind="count",
height=4,
aspect=1.5,
)
/usr/local/lib/python3.10/dist-packages/seaborn/axisgrid.py:123: UserWarning:
The figure layout has changed to tight
/usr/local/lib/python3.10/dist-packages/seaborn/axisgrid.py:123: UserWarning:
The figure layout has changed to tight
/usr/local/lib/python3.10/dist-packages/seaborn/axisgrid.py:213: UserWarning:
This figure was using a layout engine that is incompatible with subplots_adjust and/or tight_layout; not calling subplots_adjust.
fml = "nsneeze ~ alcohol + antihist + alcohol:antihist" # full patsy formulation
fml = "nsneeze ~ alcohol * antihist" # lazy, alternative patsy formulation
(mx_en, mx_ex) = pt.dmatrices(fml, df, return_type="dataframe", NA_action="raise")
pd.concat((mx_ex.head(3), mx_ex.tail(3)))
| Intercept | alcohol[T.True] | antihist[T.True] | alcohol[T.True]:antihist[T.True] | |
|---|---|---|---|---|
| 0 | 1.0 | 0.0 | 0.0 | 0.0 |
| 1 | 1.0 | 0.0 | 0.0 | 0.0 |
| 2 | 1.0 | 0.0 | 0.0 | 0.0 |
| 3997 | 1.0 | 1.0 | 1.0 | 1.0 |
| 3998 | 1.0 | 1.0 | 1.0 | 1.0 |
| 3999 | 1.0 | 1.0 | 1.0 | 1.0 |
with pm.Model() as mdl_Poisson:
# define priors, weakly informative Normal
b0 = pm.Normal("b0_intercept", mu=0, sigma=10)
b1 = pm.Normal("b1_alcohol[T.True]", mu=0, sigma=10)
b2 = pm.Normal("b2_antihist[T.True]", mu=0, sigma=10)
b3 = pm.Normal("b3_alcohol[T.True]:antihist[T.True]", mu=0, sigma=10)
# define linear model and exp link function
eta = (
b0
+ b1 * mx_ex["alcohol[T.True]"]
+ b2 * mx_ex["antihist[T.True]"]
+ b3 * mx_ex["alcohol[T.True]:antihist[T.True]"]
)
## Define Poisson likelihood
y = pm.Poisson("y", mu=np.exp(eta), observed=mx_en["nsneeze"].values)
Obtenção das amostras
with mdl_Poisson:
inf_Poisson = pm.sample(1000, tune=1000, cores=4, return_inferencedata=True)
/usr/local/lib/python3.10/dist-packages/arviz/utils.py:184: NumbaDeprecationWarning:
The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
Diagnóstico do modelo
az.plot_trace(inf_Poisson)
/usr/local/lib/python3.10/dist-packages/arviz/utils.py:184: NumbaDeprecationWarning:
The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
array([[<Axes: title={'center': 'b0_intercept'}>,
<Axes: title={'center': 'b0_intercept'}>],
[<Axes: title={'center': 'b1_alcohol[T.True]'}>,
<Axes: title={'center': 'b1_alcohol[T.True]'}>],
[<Axes: title={'center': 'b2_antihist[T.True]'}>,
<Axes: title={'center': 'b2_antihist[T.True]'}>],
[<Axes: title={'center': 'b3_alcohol[T.True]:antihist[T.True]'}>,
<Axes: title={'center': 'b3_alcohol[T.True]:antihist[T.True]'}>]],
dtype=object)
Estimação dos parâmetros
np.exp(az.summary(inf_Poisson)[["mean", "hdi_3%", "hdi_97%"]])
| mean | hdi_3% | hdi_97% | |
|---|---|---|---|
| b0_intercept | 1.044982 | 0.985112 | 1.110711 |
| b1_alcohol[T.True] | 2.953527 | 2.745601 | 3.164516 |
| b2_antihist[T.True] | 5.743105 | 5.381677 | 6.147220 |
| b3_alcohol[T.True]:antihist[T.True] | 2.029927 | 1.881370 | 2.190216 |
Exemplo original por Jonathan Sedar 2016-05-15 github.com/jonsedar
Prática 1#
Modelos Lineares Generalizados#
Modelo para óbitos por COVID-19#
Na base de dados comorbidades.csv, são apresentados dados reais de uma amostra obtida do seade-R (Fonte dos dados originais: seade-R/dados-covid-sp). Estão disponíveis as seguintes informações:
Identificação do paciente
Município
Código do IBGE
Idade
Sexo (1: feminino, 0: masculino)
Óbito (1: sim, 0: não)
Comorbidades: asma, cardiopatia, diabetes, doença hematológica, doença renal, doença hepática, doença neurológica, imumodepressão, obesidade, outros fatores de risco, pneumopatia, puérpera, síndrome de down (para cada uma delas 1: presente, 0: ausente)
Os dados faltantes foram excluídos da base original para esta análise específica, considerando que essa exclusão não afeta a representatividade da amostra.
Desenvolva uma análise exploratória para investigar a associação entre idade e óbito, e repita para sexo e óbito.
Ajuste um modelo de regressão logística com intercepto, considerando as preditoras sexo, idade, asma, cardiopatia, diabetes, doenca_renal, obesidade.
import numpy as np
import statsmodels.api as sm
from scipy import stats
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
import seaborn as sns
%matplotlib inline
import numpy as np
import pandas as pd
import statsmodels.api as sm
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/comorbidades.csv', index_col=0)
dados.head()
| nome_munic | codigo_ibge | idade | sexo | obito | asma | cardiopatia | diabetes | doenca_hematologica | doenca_hepatica | doenca_neurologica | doenca_renal | imunodepressao | obesidade | outros_fatores_de_risco | pneumopatia | puerpera | sindrome_de_down | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 66 | Ferraz de Vasconcelos | 3515707 | 86 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 97 | São Paulo | 3550308 | 62 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 100 | São José dos Campos | 3549904 | 58 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
| 207 | Mauá | 3529401 | 54 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 249 | Cajamar | 3509205 | 62 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Ajustando um MLG com resposta binária com intercepto#
Considere a divisão da base em treinamento e teste, deixando 20% das observações para teste#
# Adicionar uma coluna de uns referente ao intercepto
n=len(dados)
dados.loc[:,'const'] = np.ones(n).reshape(n,1)
dados_treino, dados_teste = train_test_split(dados,train_size = 0.8,random_state=3)
preditoras = dados_treino[['const','idade','sexo','asma','cardiopatia','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: obito No. Observations: 945
Model: GLM Df Residuals: 937
Model Family: Binomial Df Model: 7
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -575.28
Date: Wed, 17 Jan 2024 Deviance: 1150.6
Time: 02:05:48 Pearson chi2: 941.
No. Iterations: 4 Pseudo R-squ. (CS): 0.09382
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
const -3.3762 0.351 -9.609 0.000 -4.065 -2.688
idade 0.0429 0.005 8.212 0.000 0.033 0.053
sexo -0.1453 0.144 -1.011 0.312 -0.427 0.136
asma -0.3512 0.408 -0.860 0.390 -1.151 0.449
cardiopatia -0.0492 0.146 -0.337 0.736 -0.336 0.237
diabetes 0.2503 0.145 1.721 0.085 -0.035 0.535
doenca_renal 0.5812 0.296 1.961 0.050 0.000 1.162
obesidade 0.6499 0.210 3.096 0.002 0.239 1.061
================================================================================
ajustado = res.predict(preditoras)
X_teste = dados_teste[['const','idade','sexo','asma','cardiopatia','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]
predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.19933046972523166
res.aic
1166.5619547074634
Segundo modelo, excluindo cardiopatia
preditoras = dados_treino[['const','idade','sexo','asma','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: obito No. Observations: 945
Model: GLM Df Residuals: 938
Model Family: Binomial Df Model: 6
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -575.34
Date: Wed, 17 Jan 2024 Deviance: 1150.7
Time: 02:05:48 Pearson chi2: 942.
No. Iterations: 4 Pseudo R-squ. (CS): 0.09371
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
const -3.3807 0.351 -9.625 0.000 -4.069 -2.692
idade 0.0426 0.005 8.304 0.000 0.033 0.053
sexo -0.1464 0.144 -1.019 0.308 -0.428 0.135
asma -0.3497 0.409 -0.856 0.392 -1.151 0.451
diabetes 0.2489 0.145 1.712 0.087 -0.036 0.534
doenca_renal 0.5768 0.296 1.948 0.051 -0.004 1.157
obesidade 0.6495 0.210 3.093 0.002 0.238 1.061
================================================================================
ajustado = res.predict()
X_teste = dados_teste[['const','idade','sexo','asma','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]
predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.19943538803697025
res.aic
1164.6752349551355
Terceiro modelo, excluindo asma
preditoras = dados_treino[['const','idade','sexo','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: obito No. Observations: 945
Model: GLM Df Residuals: 939
Model Family: Binomial Df Model: 5
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -575.72
Date: Wed, 17 Jan 2024 Deviance: 1151.4
Time: 02:05:48 Pearson chi2: 942.
No. Iterations: 4 Pseudo R-squ. (CS): 0.09298
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
const -3.3842 0.351 -9.651 0.000 -4.071 -2.697
idade 0.0425 0.005 8.295 0.000 0.032 0.053
sexo -0.1461 0.144 -1.017 0.309 -0.428 0.135
diabetes 0.2539 0.145 1.748 0.081 -0.031 0.539
doenca_renal 0.5806 0.296 1.963 0.050 0.001 1.160
obesidade 0.6365 0.209 3.040 0.002 0.226 1.047
================================================================================
ajustado = res.predict()
X_teste = dados_teste[['const','idade','sexo','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]
predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.19962241839836906
res.aic
1163.430212617864
Quarto modelo, excluindo sexo
preditoras = dados_treino[['const','idade','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
ajustado = res.predict()
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: obito No. Observations: 945
Model: GLM Df Residuals: 940
Model Family: Binomial Df Model: 4
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -576.23
Date: Wed, 17 Jan 2024 Deviance: 1152.5
Time: 02:05:48 Pearson chi2: 942.
No. Iterations: 4 Pseudo R-squ. (CS): 0.09199
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
const -3.4253 0.348 -9.834 0.000 -4.108 -2.743
idade 0.0420 0.005 8.253 0.000 0.032 0.052
diabetes 0.2648 0.145 1.829 0.067 -0.019 0.549
doenca_renal 0.6034 0.295 2.048 0.041 0.026 1.181
obesidade 0.6230 0.209 2.987 0.003 0.214 1.032
================================================================================
Note que a significância marginal das preditoras que ficaram muda em relação aos modelos anteriores. Neste momento paramos as exclusões e vamos manter as preditoras.
X_teste = dados_teste[['const','idade','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]
predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.20006018415344806
res.aic
1162.4668937333217
Análise de diagnóstico para o modelo “final”, agora com a base toda
preditoras = dados[['const','idade','diabetes','doenca_renal', 'obesidade']]
resposta = dados[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
ajustado = res.predict()
print(res.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: obito No. Observations: 1182
Model: GLM Df Residuals: 1177
Model Family: Binomial Df Model: 4
Link Function: Logit Scale: 1.0000
Method: IRLS Log-Likelihood: -716.15
Date: Wed, 17 Jan 2024 Deviance: 1432.3
Time: 02:05:48 Pearson chi2: 1.21e+03
No. Iterations: 4 Pseudo R-squ. (CS): 0.09374
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
const -3.5240 0.315 -11.194 0.000 -4.141 -2.907
idade 0.0432 0.005 9.445 0.000 0.034 0.052
diabetes 0.2758 0.130 2.121 0.034 0.021 0.531
doenca_renal 0.4378 0.260 1.683 0.092 -0.072 0.948
obesidade 0.6268 0.185 3.397 0.001 0.265 0.988
================================================================================
Qual a interpretação dos parâmetros?
Podemos fazê-la com a razão de chances.
Exemplo:
Para a preditora obesidade, \(\exp(0.6268) = 1.87\), o que indica um aumento de 87% na chance de óbito para pacientes com obesidade em relação a pacientes que não apresentam essa característica.
Alguns códigos para a análise de diagnóstico.
# Gráfico de pontos de alavanca
fig, ax = plt.subplots()
plt.plot(res.get_hat_matrix_diag(), '.')
ax.set_title('Pontos de alavanca')
ax.set_ylabel('$h_{ii}$')
ax.set_xlabel('índice das observações');
plt.show()
# Gráfico de Resíduo Componente do desvio
fig, ax = plt.subplots()
plt.plot(ajustado,res.resid_deviance, '.')
ax.set_ylabel('Resíduo componente do desvio')
ax.set_xlabel('valor ajustado');
plt.show()
# x and y given as DataFrame columns
import plotly.express as px
fig = px.scatter(x = ajustado, y=res.resid_deviance)
fig.show()